mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-02-19 03:14:29 -05:00
Compare commits
57 Commits
dependabot
...
adk-python
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3928708e26 | ||
|
|
d6af2907fd | ||
|
|
3684efd512 | ||
|
|
7a6d0c12e9 | ||
|
|
57b77bca09 | ||
|
|
276cf604a2 | ||
|
|
fe198308d3 | ||
|
|
a206f1afb6 | ||
|
|
1a62677717 | ||
|
|
c37958af68 | ||
|
|
29bf66a8a3 | ||
|
|
b38d36aeef | ||
|
|
acf56382d7 | ||
|
|
fe2dd13968 | ||
|
|
bf3fb86fd2 | ||
|
|
d3d62a76d4 | ||
|
|
8fa8840d77 | ||
|
|
1ecc3cd604 | ||
|
|
acbb9c15ed | ||
|
|
7e175c62a0 | ||
|
|
b0947b2adc | ||
|
|
3665b78a4b | ||
|
|
d7d5e75324 | ||
|
|
ad05a12a67 | ||
|
|
0b23fc950f | ||
|
|
632dd10180 | ||
|
|
9771aa47df | ||
|
|
a212aedd19 | ||
|
|
9210e5555c | ||
|
|
b43af71793 | ||
|
|
da1f463dd1 | ||
|
|
3265f7e3a6 | ||
|
|
336743f747 | ||
|
|
911069ae8d | ||
|
|
cee59d52c3 | ||
|
|
9517daba09 | ||
|
|
3c61ee0597 | ||
|
|
19271eb9ee | ||
|
|
3a150c77ca | ||
|
|
ca6f31a192 | ||
|
|
d7faf7700f | ||
|
|
37a60ea2a6 | ||
|
|
8de16976ae | ||
|
|
49cb2f39f7 | ||
|
|
f169874e53 | ||
|
|
db8c3a3c77 | ||
|
|
8b33b0c67f | ||
|
|
35fa73516b | ||
|
|
66df3bfd21 | ||
|
|
73e0edc3cd | ||
|
|
3f32a9aab6 | ||
|
|
28006fc9b2 | ||
|
|
56c69131b4 | ||
|
|
ad4a509340 | ||
|
|
d39acac96c | ||
|
|
6df2ad28a9 | ||
|
|
8416378613 |
2
.github/workflows/link_checker.yaml
vendored
2
.github/workflows/link_checker.yaml
vendored
@@ -114,7 +114,7 @@ jobs:
|
||||
|
||||
- name: Create PR Comment
|
||||
if: env.HAS_CHANGES == 'true' && steps.lychee-check.outcome == 'failure'
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
|
||||
@@ -140,17 +140,16 @@
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
||||
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -190,7 +189,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
@@ -203,7 +201,6 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
@@ -239,7 +236,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
@@ -248,7 +244,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
@@ -270,7 +265,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -279,7 +273,6 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -288,7 +281,6 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
@@ -300,7 +292,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
@@ -358,10 +349,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
@@ -388,7 +378,6 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
@@ -423,7 +412,6 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
@@ -447,7 +435,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
@@ -485,7 +472,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -509,7 +495,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -521,7 +506,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
@@ -536,7 +520,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
@@ -599,7 +582,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -608,7 +590,6 @@
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@@ -617,7 +598,6 @@
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
|
||||
@@ -3231,9 +3231,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
|
||||
@@ -133,3 +133,4 @@ instead of hardcoding your secrets into the configuration file.
|
||||
| user | string | true | Name of the Postgres user to connect as (e.g. "my-pg-user"). |
|
||||
| password | string | true | Password of the Postgres user (e.g. "my-password"). |
|
||||
| queryParams | map[string]string | false | Raw query to be added to the db connection string. |
|
||||
| queryExecMode | string | false | pgx query execution mode. Valid values: `cache_statement` (default), `cache_describe`, `describe_exec`, `exec`, `simple_protocol`. Useful with connection poolers that don't support prepared statement caching. |
|
||||
|
||||
@@ -4,8 +4,8 @@ linkTitle: "Redis"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
Redis is a in-memory data structure store.
|
||||
|
||||
Redis is a in-memory data structure store.
|
||||
|
||||
---
|
||||
|
||||
## About
|
||||
@@ -44,6 +44,9 @@ password: ${MY_AUTH_STRING} # Omit this field if you don't have a password.
|
||||
# database: 0
|
||||
# clusterEnabled: false
|
||||
# useGCPIAM: false
|
||||
# tls:
|
||||
# enabled: false
|
||||
# insecureSkipVerify: false
|
||||
```
|
||||
|
||||
{{< notice tip >}}
|
||||
@@ -61,7 +64,7 @@ Here is an example tools.yaml config with [AUTH][auth] enabled:
|
||||
```yaml
|
||||
kind: sources
|
||||
name: my-redis-cluster-instance
|
||||
type: memorystore-redis
|
||||
type: redis
|
||||
address:
|
||||
- 127.0.0.1:6379
|
||||
password: ${MY_AUTH_STRING}
|
||||
@@ -78,7 +81,7 @@ using IAM authentication:
|
||||
```yaml
|
||||
kind: sources
|
||||
name: my-redis-cluster-instance
|
||||
type: memorystore-redis
|
||||
type: redis
|
||||
address:
|
||||
- 127.0.0.1:6379
|
||||
useGCPIAM: true
|
||||
@@ -89,14 +92,16 @@ clusterEnabled: true
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|----------------|:--------:|:------------:|---------------------------------------------------------------------------------------------------------------------------------|
|
||||
| type | string | true | Must be "memorystore-redis". |
|
||||
| address | string | true | Primary endpoint for the Memorystore Redis instance to connect to. |
|
||||
| username | string | false | If you are using a non-default user, specify the user name here. If you are using Memorystore for Redis, leave this field blank |
|
||||
| password | string | false | If you have [Redis AUTH][auth] enabled, specify the AUTH string here |
|
||||
| database | int | false | The Redis database to connect to. Not applicable for cluster enabled instances. The default database is `0`. |
|
||||
| clusterEnabled | bool | false | Set it to `true` if using a Redis Cluster instance. Defaults to `false`. |
|
||||
| useGCPIAM | string | false | Set it to `true` if you are using GCP's IAM authentication. Defaults to `false`. |
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|------------------------|:--------:|:------------:|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| type | string | true | Must be "redis". |
|
||||
| address | string | true | Primary endpoint for the Memorystore Redis instance to connect to. |
|
||||
| username | string | false | If you are using a non-default user, specify the user name here. If you are using Memorystore for Redis, leave this field blank |
|
||||
| password | string | false | If you have [Redis AUTH][auth] enabled, specify the AUTH string here |
|
||||
| database | int | false | The Redis database to connect to. Not applicable for cluster enabled instances. The default database is `0`. |
|
||||
| tls.enabled | bool | false | Set it to `true` to enable TLS for the Redis connection. Defaults to `false`. |
|
||||
| tls.insecureSkipVerify | bool | false | Set it to `true` to skip TLS certificate verification. **Warning:** This is insecure and not recommended for production. Defaults to `false`. |
|
||||
| clusterEnabled | bool | false | Set it to `true` if using a Redis Cluster instance. Defaults to `false`. |
|
||||
| useGCPIAM | bool | false | Set it to `true` if you are using GCP's IAM authentication. Defaults to `false`. |
|
||||
|
||||
[auth]: https://cloud.google.com/memorystore/docs/redis/about-redis-auth
|
||||
|
||||
@@ -16,7 +16,14 @@ This guide demonstrates how to implement these patterns in your Toolbox applicat
|
||||
|
||||
{{< tabpane persist=header >}}
|
||||
{{% tab header="ADK" text=true %}}
|
||||
Coming soon.
|
||||
The following example demonstrates how to use `ToolboxToolset` with ADK's pre and post processing hooks to implement pre and post processing for tool calls.
|
||||
|
||||
```py
|
||||
{{< include "python/adk/agent.py" >}}
|
||||
```
|
||||
You can also add model-level (`before_model_callback`, `after_model_callback`) and agent-level (`before_agent_callback`, `after_agent_callback`) hooks to intercept messages at different stages of the execution loop.
|
||||
|
||||
For more information, see the [ADK Callbacks documentation](https://google.github.io/adk-docs/callbacks/types-of-callbacks/).
|
||||
{{% /tab %}}
|
||||
{{% tab header="Langchain" text=true %}}
|
||||
The following example demonstrates how to use `ToolboxClient` with LangChain's middleware to implement pre- and post- processing for tool calls.
|
||||
|
||||
137
docs/en/samples/pre_post_processing/python/adk/agent.py
Normal file
137
docs/en/samples/pre_post_processing/python/adk/agent.py
Normal file
@@ -0,0 +1,137 @@
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
from copy import deepcopy
|
||||
|
||||
from google.adk import Agent
|
||||
from google.adk.apps import App
|
||||
from google.adk.runners import Runner
|
||||
from google.adk.sessions.in_memory_session_service import InMemorySessionService
|
||||
from google.adk.tools.tool_context import ToolContext
|
||||
from google.genai import types
|
||||
from toolbox_adk import CredentialStrategy, ToolboxToolset, ToolboxTool
|
||||
|
||||
SYSTEM_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 ids while performing any
|
||||
searches. This is very important for any operations. For any bookings or
|
||||
cancellations, please provide the appropriate confirmation. Be sure to
|
||||
update checkin or checkout dates if mentioned by the user.
|
||||
Don't ask for confirmations from the user.
|
||||
"""
|
||||
|
||||
|
||||
# Pre processing
|
||||
async def enfore_business_rules(
|
||||
tool: ToolboxTool, args: Dict[str, Any], tool_context: ToolContext
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Callback fired before a tool is executed.
|
||||
Enforces business logic: Max stay duration is 14 days.
|
||||
"""
|
||||
tool_name = tool.name
|
||||
print(f"POLICY CHECK: Intercepting '{tool_name}'")
|
||||
|
||||
if tool_name == "update-hotel" and "checkin_date" in args and "checkout_date" in args:
|
||||
start = datetime.fromisoformat(args["checkin_date"])
|
||||
end = datetime.fromisoformat(args["checkout_date"])
|
||||
duration = (end - start).days
|
||||
|
||||
if duration > 14:
|
||||
print("BLOCKED: Stay too long")
|
||||
return {"result": "Error: Maximum stay duration is 14 days."}
|
||||
return None
|
||||
|
||||
|
||||
# Post processing
|
||||
async def enrich_response(
|
||||
tool: ToolboxTool,
|
||||
args: Dict[str, Any],
|
||||
tool_context: ToolContext,
|
||||
tool_response: Any,
|
||||
) -> Optional[Any]:
|
||||
"""
|
||||
Callback fired after a tool execution.
|
||||
Enriches response for successful bookings.
|
||||
"""
|
||||
if isinstance(tool_response, dict):
|
||||
result = tool_response.get("result", "")
|
||||
elif isinstance(tool_response, str):
|
||||
result = tool_response
|
||||
else:
|
||||
return None
|
||||
|
||||
tool_name = tool.name
|
||||
if isinstance(result, str) and "Error" not in result:
|
||||
if tool_name == "book-hotel":
|
||||
loyalty_bonus = 500
|
||||
enriched_result = f"Booking Confirmed!\n You earned {loyalty_bonus} Loyalty Points with this stay.\n\nSystem Details: {result}"
|
||||
|
||||
if isinstance(tool_response, dict):
|
||||
modified_response = deepcopy(tool_response)
|
||||
modified_response["result"] = enriched_result
|
||||
return modified_response
|
||||
else:
|
||||
return enriched_result
|
||||
return None
|
||||
|
||||
|
||||
async def run_chat_turn(
|
||||
runner: Runner, session_id: str, user_id: str, message_text: str
|
||||
):
|
||||
"""Executes a single chat turn and prints the interaction."""
|
||||
print(f"\nUSER: '{message_text}'")
|
||||
response_text = ""
|
||||
async for event in runner.run_async(
|
||||
user_id=user_id,
|
||||
session_id=session_id,
|
||||
new_message=types.Content(role="user", parts=[types.Part(text=message_text)]),
|
||||
):
|
||||
if event.content and event.content.parts:
|
||||
for part in event.content.parts:
|
||||
if part.text:
|
||||
response_text += part.text
|
||||
|
||||
print(f"AI: {response_text}")
|
||||
|
||||
|
||||
async def main():
|
||||
toolset = ToolboxToolset(
|
||||
server_url="http://127.0.0.1:5000",
|
||||
toolset_name="my-toolset",
|
||||
credentials=CredentialStrategy.toolbox_identity(),
|
||||
)
|
||||
tools = await toolset.get_tools()
|
||||
root_agent = Agent(
|
||||
name="root_agent",
|
||||
model="gemini-2.5-flash",
|
||||
instruction=SYSTEM_PROMPT,
|
||||
tools=tools,
|
||||
# add any pre and post processing callbacks
|
||||
before_tool_callback=enfore_business_rules,
|
||||
after_tool_callback=enrich_response,
|
||||
)
|
||||
app = App(root_agent=root_agent, name="my_agent")
|
||||
runner = Runner(app=app, session_service=InMemorySessionService())
|
||||
session_id = "test-session"
|
||||
user_id = "test-user"
|
||||
await runner.session_service.create_session(
|
||||
app_name=app.name, user_id=user_id, session_id=session_id
|
||||
)
|
||||
|
||||
# First turn: Successful booking
|
||||
await run_chat_turn(runner, session_id, user_id, "Book hotel with id 3.")
|
||||
print("-" * 50)
|
||||
# Second turn: Policy violation (stay > 14 days)
|
||||
await run_chat_turn(
|
||||
runner,
|
||||
session_id,
|
||||
user_id,
|
||||
"Book a hotel with id 5 with checkin date 2025-01-18 and checkout date 2025-02-10",
|
||||
)
|
||||
await toolset.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,3 @@
|
||||
google-adk[toolbox]==1.23.0
|
||||
toolbox-adk==0.5.8
|
||||
google-genai==1.62.0
|
||||
@@ -87,8 +87,29 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 15px;
|
||||
align-items: center;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
min-width: 200px;
|
||||
max-width: 50vw;
|
||||
overflow: visible;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.resize-handle {
|
||||
position: absolute;
|
||||
right: -2px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 4px;
|
||||
cursor: ew-resize;
|
||||
background-color: transparent;
|
||||
z-index: 10;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.resize-handle:hover,
|
||||
.resize-handle.active {
|
||||
background-color: var(--toolbox-blue);
|
||||
}
|
||||
|
||||
.nav-logo {
|
||||
@@ -626,10 +647,13 @@ body {
|
||||
.search-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin-bottom: 15px;
|
||||
box-sizing: border-box;
|
||||
|
||||
#toolset-search-input {
|
||||
flex-grow: 1;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 20px 0 0 20px;
|
||||
@@ -637,6 +661,7 @@ body {
|
||||
font-family: inherit;
|
||||
font-size: 0.9em;
|
||||
color: var(--text-primary-gray);
|
||||
box-sizing: border-box;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
||||
116
internal/server/static/js/resize.js
Normal file
116
internal/server/static/js/resize.js
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
const STORAGE_KEY = 'toolbox-second-nav-width';
|
||||
const DEFAULT_WIDTH = 250;
|
||||
const MIN_WIDTH = 200;
|
||||
const MAX_WIDTH_PERCENT = 50;
|
||||
|
||||
/**
|
||||
* Creates and attaches a resize handle to the second navigation panel
|
||||
*/
|
||||
export function initializeResize() {
|
||||
const secondNav = document.querySelector('.second-nav');
|
||||
if (!secondNav) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create resize handle
|
||||
const resizeHandle = document.createElement('div');
|
||||
resizeHandle.className = 'resize-handle';
|
||||
resizeHandle.setAttribute('aria-label', 'Resize panel');
|
||||
secondNav.appendChild(resizeHandle);
|
||||
|
||||
// Load saved width or use default
|
||||
let initialWidth = DEFAULT_WIDTH;
|
||||
try {
|
||||
const savedWidth = localStorage.getItem(STORAGE_KEY);
|
||||
if (savedWidth) {
|
||||
const parsed = parseInt(savedWidth, 10);
|
||||
if (!isNaN(parsed) && parsed >= MIN_WIDTH) {
|
||||
initialWidth = parsed;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// localStorage may be unavailable in private browsing mode
|
||||
console.warn('Failed to load saved panel width:', e);
|
||||
}
|
||||
setPanelWidth(secondNav, initialWidth);
|
||||
|
||||
// Setup resize functionality
|
||||
let startX = 0;
|
||||
let startWidth = 0;
|
||||
|
||||
const onMouseMove = (e) => {
|
||||
const deltaX = e.clientX - startX;
|
||||
const newWidth = startWidth + deltaX;
|
||||
const maxWidth = (window.innerWidth * MAX_WIDTH_PERCENT) / 100;
|
||||
|
||||
const clampedWidth = Math.max(MIN_WIDTH, Math.min(newWidth, maxWidth));
|
||||
setPanelWidth(secondNav, clampedWidth);
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
|
||||
resizeHandle.classList.remove('active');
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
|
||||
// Save width to localStorage
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, secondNav.offsetWidth.toString());
|
||||
} catch (e) {
|
||||
// localStorage may be unavailable in private browsing mode
|
||||
console.warn('Failed to save panel width:', e);
|
||||
}
|
||||
};
|
||||
|
||||
resizeHandle.addEventListener('mousedown', (e) => {
|
||||
startX = e.clientX;
|
||||
startWidth = secondNav.offsetWidth;
|
||||
resizeHandle.classList.add('active');
|
||||
document.body.style.cursor = 'ew-resize';
|
||||
document.body.style.userSelect = 'none';
|
||||
e.preventDefault();
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
});
|
||||
|
||||
// Handle window resize to enforce max width
|
||||
window.addEventListener('resize', () => {
|
||||
const currentWidth = secondNav.offsetWidth;
|
||||
const maxWidth = (window.innerWidth * MAX_WIDTH_PERCENT) / 100;
|
||||
|
||||
if (currentWidth > maxWidth) {
|
||||
setPanelWidth(secondNav, maxWidth);
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, maxWidth.toString());
|
||||
} catch (e) {
|
||||
// localStorage may be unavailable in private browsing mode
|
||||
console.warn('Failed to save panel width:', e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the width of the panel and updates flex property
|
||||
*/
|
||||
function setPanelWidth(panel, width) {
|
||||
panel.style.flex = `0 0 ${width}px`;
|
||||
}
|
||||
|
||||
@@ -22,12 +22,16 @@
|
||||
<script type="module" src="/ui/js/tools.js"></script>
|
||||
<script src="/ui/js/navbar.js"></script>
|
||||
<script src="/ui/js/mainContent.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
<script type="module">
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const navbarContainer = document.getElementById('navbar-container');
|
||||
const activeNav = navbarContainer.getAttribute('data-active-nav');
|
||||
renderNavbar('navbar-container', activeNav);
|
||||
renderMainContent('main-content-container', 'tool-display-area', getToolInstructions())
|
||||
renderMainContent('main-content-container', 'tool-display-area', getToolInstructions());
|
||||
|
||||
// Initialize resize functionality
|
||||
const { initializeResize } = await import('/ui/js/resize.js');
|
||||
initializeResize();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -29,12 +29,16 @@
|
||||
<script type="module" src="/ui/js/toolsets.js"></script>
|
||||
<script src="/ui/js/navbar.js"></script>
|
||||
<script src="/ui/js/mainContent.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
<script type="module">
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const navbarContainer = document.getElementById('navbar-container');
|
||||
const activeNav = navbarContainer.getAttribute('data-active-nav');
|
||||
renderNavbar('navbar-container', activeNav);
|
||||
renderMainContent('main-content-container', 'tool-display-area', getToolsetInstructions());
|
||||
|
||||
// Initialize resize functionality
|
||||
const { initializeResize } = await import('/ui/js/resize.js');
|
||||
initializeResize();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
"github.com/googleapis/genai-toolbox/internal/util"
|
||||
"github.com/googleapis/genai-toolbox/internal/util/orderedmap"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
@@ -48,14 +49,15 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Type string `yaml:"type" validate:"required"`
|
||||
Host string `yaml:"host" validate:"required"`
|
||||
Port string `yaml:"port" validate:"required"`
|
||||
User string `yaml:"user" validate:"required"`
|
||||
Password string `yaml:"password" validate:"required"`
|
||||
Database string `yaml:"database" validate:"required"`
|
||||
QueryParams map[string]string `yaml:"queryParams"`
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Type string `yaml:"type" validate:"required"`
|
||||
Host string `yaml:"host" validate:"required"`
|
||||
Port string `yaml:"port" validate:"required"`
|
||||
User string `yaml:"user" validate:"required"`
|
||||
Password string `yaml:"password" validate:"required"`
|
||||
Database string `yaml:"database" validate:"required"`
|
||||
QueryParams map[string]string `yaml:"queryParams"`
|
||||
QueryExecMode string `yaml:"queryExecMode" validate:"omitempty,oneof=cache_statement cache_describe describe_exec exec simple_protocol"`
|
||||
}
|
||||
|
||||
func (r Config) SourceConfigType() string {
|
||||
@@ -63,7 +65,7 @@ func (r Config) SourceConfigType() string {
|
||||
}
|
||||
|
||||
func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
|
||||
pool, err := initPostgresConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Database, r.QueryParams)
|
||||
pool, err := initPostgresConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Database, r.QueryParams, r.QueryExecMode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create pool: %w", err)
|
||||
}
|
||||
@@ -126,7 +128,7 @@ func (s *Source) RunSQL(ctx context.Context, statement string, params []any) (an
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func initPostgresConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, pass, dbname string, queryParams map[string]string) (*pgxpool.Pool, error) {
|
||||
func initPostgresConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, pass, dbname string, queryParams map[string]string, queryExecMode string) (*pgxpool.Pool, error) {
|
||||
//nolint:all // Reassigned ctx
|
||||
ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceType, name)
|
||||
defer span.End()
|
||||
@@ -150,7 +152,18 @@ func initPostgresConnectionPool(ctx context.Context, tracer trace.Tracer, name,
|
||||
Path: dbname,
|
||||
RawQuery: ConvertParamMapToRawQuery(queryParams),
|
||||
}
|
||||
pool, err := pgxpool.New(ctx, url.String())
|
||||
config, err := pgxpool.ParseConfig(url.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse connection uri: %w", err)
|
||||
}
|
||||
|
||||
execMode, err := ParseQueryExecMode(queryExecMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.ConnConfig.DefaultQueryExecMode = execMode
|
||||
|
||||
pool, err := pgxpool.NewWithConfig(ctx, config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create connection pool: %w", err)
|
||||
}
|
||||
@@ -165,3 +178,20 @@ func ConvertParamMapToRawQuery(queryParams map[string]string) string {
|
||||
}
|
||||
return strings.Join(queryArray, "&")
|
||||
}
|
||||
|
||||
func ParseQueryExecMode(queryExecMode string) (pgx.QueryExecMode, error) {
|
||||
switch queryExecMode {
|
||||
case "", "cache_statement":
|
||||
return pgx.QueryExecModeCacheStatement, nil
|
||||
case "cache_describe":
|
||||
return pgx.QueryExecModeCacheDescribe, nil
|
||||
case "describe_exec":
|
||||
return pgx.QueryExecModeDescribeExec, nil
|
||||
case "exec":
|
||||
return pgx.QueryExecModeExec, nil
|
||||
case "simple_protocol":
|
||||
return pgx.QueryExecModeSimpleProtocol, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid queryExecMode %q: must be one of %q, %q, %q, %q, or %q", queryExecMode, "cache_statement", "cache_describe", "describe_exec", "exec", "simple_protocol")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources/postgres"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
func TestParseFromYamlPostgres(t *testing.T) {
|
||||
@@ -88,6 +89,32 @@ func TestParseFromYamlPostgres(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "example with query exec mode",
|
||||
in: `
|
||||
kind: sources
|
||||
name: my-pg-instance
|
||||
type: postgres
|
||||
host: my-host
|
||||
port: my-port
|
||||
database: my_db
|
||||
user: my_user
|
||||
password: my_pass
|
||||
queryExecMode: simple_protocol
|
||||
`,
|
||||
want: map[string]sources.SourceConfig{
|
||||
"my-pg-instance": postgres.Config{
|
||||
Name: "my-pg-instance",
|
||||
Type: postgres.SourceType,
|
||||
Host: "my-host",
|
||||
Port: "my-port",
|
||||
Database: "my_db",
|
||||
User: "my_user",
|
||||
Password: "my_pass",
|
||||
QueryExecMode: "simple_protocol",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
@@ -137,6 +164,21 @@ func TestFailParseFromYaml(t *testing.T) {
|
||||
`,
|
||||
err: "error unmarshaling sources: unable to parse source \"my-pg-instance\" as \"postgres\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag",
|
||||
},
|
||||
{
|
||||
desc: "invalid query exec mode",
|
||||
in: `
|
||||
kind: sources
|
||||
name: my-pg-instance
|
||||
type: postgres
|
||||
host: my-host
|
||||
port: my-port
|
||||
database: my_db
|
||||
user: my_user
|
||||
password: my_pass
|
||||
queryExecMode: invalid_mode
|
||||
`,
|
||||
err: "error unmarshaling sources: unable to parse source \"my-pg-instance\" as \"postgres\": [6:16] Key: 'Config.QueryExecMode' Error:Field validation for 'QueryExecMode' failed on the 'oneof' tag\n 3 | name: my-pg-instance\n 4 | password: my_pass\n 5 | port: my-port\n> 6 | queryExecMode: invalid_mode\n ^\n 7 | type: postgres\n 8 | user: my_user",
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
@@ -193,3 +235,32 @@ func TestConvertParamMapToRawQuery(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseQueryExecMode(t *testing.T) {
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want pgx.QueryExecMode
|
||||
wantErr bool
|
||||
}{
|
||||
{desc: "empty (default)", in: "", want: pgx.QueryExecModeCacheStatement},
|
||||
{desc: "cache_statement", in: "cache_statement", want: pgx.QueryExecModeCacheStatement},
|
||||
{desc: "cache_describe", in: "cache_describe", want: pgx.QueryExecModeCacheDescribe},
|
||||
{desc: "describe_exec", in: "describe_exec", want: pgx.QueryExecModeDescribeExec},
|
||||
{desc: "exec", in: "exec", want: pgx.QueryExecModeExec},
|
||||
{desc: "simple_protocol", in: "simple_protocol", want: pgx.QueryExecModeSimpleProtocol},
|
||||
{desc: "invalid mode", in: "invalid_mode", wantErr: true},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got, err := postgres.ParseQueryExecMode(tc.in)
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Fatalf("parseQueryExecMode() error = %v, wantErr %v", err, tc.wantErr)
|
||||
}
|
||||
if !tc.wantErr && got != tc.want {
|
||||
t.Errorf("parseQueryExecMode() = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -44,14 +45,20 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Type string `yaml:"type" validate:"required"`
|
||||
Address []string `yaml:"address" validate:"required"`
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
Database int `yaml:"database"`
|
||||
UseGCPIAM bool `yaml:"useGCPIAM"`
|
||||
ClusterEnabled bool `yaml:"clusterEnabled"`
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Type string `yaml:"type" validate:"required"`
|
||||
Address []string `yaml:"address" validate:"required"`
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
Database int `yaml:"database"`
|
||||
UseGCPIAM bool `yaml:"useGCPIAM"`
|
||||
ClusterEnabled bool `yaml:"clusterEnabled"`
|
||||
TLS TLSConfig `yaml:"tls"`
|
||||
}
|
||||
|
||||
type TLSConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
InsecureSkipVerify bool `yaml:"insecureSkipVerify"`
|
||||
}
|
||||
|
||||
func (r Config) SourceConfigType() string {
|
||||
@@ -91,6 +98,13 @@ func initRedisClient(ctx context.Context, r Config) (RedisClient, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var tlsConfig *tls.Config
|
||||
if r.TLS.Enabled {
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: r.TLS.InsecureSkipVerify,
|
||||
}
|
||||
}
|
||||
|
||||
var client RedisClient
|
||||
var err error
|
||||
if r.ClusterEnabled {
|
||||
@@ -104,6 +118,7 @@ func initRedisClient(ctx context.Context, r Config) (RedisClient, error) {
|
||||
CredentialsProviderContext: authFn,
|
||||
Username: r.Username,
|
||||
Password: r.Password,
|
||||
TLSConfig: tlsConfig,
|
||||
})
|
||||
err = clusterClient.ForEachShard(ctx, func(ctx context.Context, shard *redis.Client) error {
|
||||
return shard.Ping(ctx).Err()
|
||||
@@ -125,6 +140,7 @@ func initRedisClient(ctx context.Context, r Config) (RedisClient, error) {
|
||||
CredentialsProviderContext: authFn,
|
||||
Username: r.Username,
|
||||
Password: r.Password,
|
||||
TLSConfig: tlsConfig,
|
||||
})
|
||||
_, err = standaloneClient.Ping(ctx).Result()
|
||||
if err != nil {
|
||||
|
||||
@@ -63,6 +63,9 @@ func TestParseFromYamlRedis(t *testing.T) {
|
||||
database: 1
|
||||
useGCPIAM: true
|
||||
clusterEnabled: true
|
||||
tls:
|
||||
enabled: true
|
||||
insecureSkipVerify: true
|
||||
`,
|
||||
want: map[string]sources.SourceConfig{
|
||||
"my-redis-instance": redis.Config{
|
||||
@@ -73,6 +76,10 @@ func TestParseFromYamlRedis(t *testing.T) {
|
||||
Database: 1,
|
||||
ClusterEnabled: true,
|
||||
UseGCPIAM: true,
|
||||
TLS: redis.TLSConfig{
|
||||
Enabled: true,
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user