This is page 8 of 45. Use http://codebase.md/googleapis/genai-toolbox?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .ci │ ├── continuous.release.cloudbuild.yaml │ ├── generate_release_table.sh │ ├── integration.cloudbuild.yaml │ ├── quickstart_test │ │ ├── go.integration.cloudbuild.yaml │ │ ├── js.integration.cloudbuild.yaml │ │ ├── py.integration.cloudbuild.yaml │ │ ├── run_go_tests.sh │ │ ├── run_js_tests.sh │ │ ├── run_py_tests.sh │ │ └── setup_hotels_sample.sql │ ├── test_with_coverage.sh │ └── versioned.release.cloudbuild.yaml ├── .github │ ├── auto-label.yaml │ ├── blunderbuss.yml │ ├── CODEOWNERS │ ├── header-checker-lint.yml │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── feature_request.yml │ │ └── question.yml │ ├── label-sync.yml │ ├── labels.yaml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── release-please.yml │ ├── renovate.json5 │ ├── sync-repo-settings.yaml │ └── workflows │ ├── cloud_build_failure_reporter.yml │ ├── deploy_dev_docs.yaml │ ├── deploy_previous_version_docs.yaml │ ├── deploy_versioned_docs.yaml │ ├── docs_deploy.yaml │ ├── docs_preview_clean.yaml │ ├── docs_preview_deploy.yaml │ ├── lint.yaml │ ├── schedule_reporter.yml │ ├── sync-labels.yaml │ └── tests.yaml ├── .gitignore ├── .gitmodules ├── .golangci.yaml ├── .hugo │ ├── archetypes │ │ └── default.md │ ├── assets │ │ ├── icons │ │ │ └── logo.svg │ │ └── scss │ │ ├── _styles_project.scss │ │ └── _variables_project.scss │ ├── go.mod │ ├── go.sum │ ├── hugo.toml │ ├── layouts │ │ ├── _default │ │ │ └── home.releases.releases │ │ ├── index.llms-full.txt │ │ ├── index.llms.txt │ │ ├── partials │ │ │ ├── hooks │ │ │ │ └── head-end.html │ │ │ ├── navbar-version-selector.html │ │ │ ├── page-meta-links.html │ │ │ └── td │ │ │ └── render-heading.html │ │ ├── robot.txt │ │ └── shortcodes │ │ ├── include.html │ │ ├── ipynb.html │ │ └── regionInclude.html │ ├── package-lock.json │ ├── package.json │ └── static │ ├── favicons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── favicon.ico │ └── js │ └── w3.js ├── CHANGELOG.md ├── cmd │ ├── options_test.go │ ├── options.go │ ├── root_test.go │ ├── root.go │ └── version.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEVELOPER.md ├── Dockerfile ├── docs │ └── en │ ├── _index.md │ ├── about │ │ ├── _index.md │ │ └── faq.md │ ├── concepts │ │ ├── _index.md │ │ └── telemetry │ │ ├── index.md │ │ ├── telemetry_flow.png │ │ └── telemetry_traces.png │ ├── getting-started │ │ ├── _index.md │ │ ├── colab_quickstart.ipynb │ │ ├── configure.md │ │ ├── introduction │ │ │ ├── _index.md │ │ │ └── architecture.png │ │ ├── local_quickstart_go.md │ │ ├── local_quickstart_js.md │ │ ├── local_quickstart.md │ │ ├── mcp_quickstart │ │ │ ├── _index.md │ │ │ ├── inspector_tools.png │ │ │ └── inspector.png │ │ └── quickstart │ │ ├── go │ │ │ ├── genAI │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ ├── genkit │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ ├── langchain │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ ├── openAI │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ └── quickstart_test.go │ │ ├── golden.txt │ │ ├── js │ │ │ ├── genAI │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ ├── genkit │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ ├── langchain │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ ├── llamaindex │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ └── quickstart.test.js │ │ ├── python │ │ │ ├── __init__.py │ │ │ ├── adk │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ ├── core │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ ├── langchain │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ ├── llamaindex │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ └── quickstart_test.py │ │ └── shared │ │ ├── cloud_setup.md │ │ ├── configure_toolbox.md │ │ └── database_setup.md │ ├── how-to │ │ ├── _index.md │ │ ├── connect_via_geminicli.md │ │ ├── connect_via_mcp.md │ │ ├── connect-ide │ │ │ ├── _index.md │ │ │ ├── alloydb_pg_admin_mcp.md │ │ │ ├── alloydb_pg_mcp.md │ │ │ ├── bigquery_mcp.md │ │ │ ├── cloud_sql_mssql_admin_mcp.md │ │ │ ├── cloud_sql_mssql_mcp.md │ │ │ ├── cloud_sql_mysql_admin_mcp.md │ │ │ ├── cloud_sql_mysql_mcp.md │ │ │ ├── cloud_sql_pg_admin_mcp.md │ │ │ ├── cloud_sql_pg_mcp.md │ │ │ ├── firestore_mcp.md │ │ │ ├── looker_mcp.md │ │ │ ├── mssql_mcp.md │ │ │ ├── mysql_mcp.md │ │ │ ├── neo4j_mcp.md │ │ │ ├── postgres_mcp.md │ │ │ ├── spanner_mcp.md │ │ │ └── sqlite_mcp.md │ │ ├── deploy_docker.md │ │ ├── deploy_gke.md │ │ ├── deploy_toolbox.md │ │ ├── export_telemetry.md │ │ └── toolbox-ui │ │ ├── edit-headers.gif │ │ ├── edit-headers.png │ │ ├── index.md │ │ ├── optional-param-checked.png │ │ ├── optional-param-unchecked.png │ │ ├── run-tool.gif │ │ ├── tools.png │ │ └── toolsets.png │ ├── reference │ │ ├── _index.md │ │ ├── cli.md │ │ └── prebuilt-tools.md │ ├── resources │ │ ├── _index.md │ │ ├── authServices │ │ │ ├── _index.md │ │ │ └── google.md │ │ ├── sources │ │ │ ├── _index.md │ │ │ ├── alloydb-admin.md │ │ │ ├── alloydb-pg.md │ │ │ ├── bigquery.md │ │ │ ├── bigtable.md │ │ │ ├── cassandra.md │ │ │ ├── clickhouse.md │ │ │ ├── cloud-monitoring.md │ │ │ ├── cloud-sql-admin.md │ │ │ ├── cloud-sql-mssql.md │ │ │ ├── cloud-sql-mysql.md │ │ │ ├── cloud-sql-pg.md │ │ │ ├── couchbase.md │ │ │ ├── dataplex.md │ │ │ ├── dgraph.md │ │ │ ├── firebird.md │ │ │ ├── firestore.md │ │ │ ├── http.md │ │ │ ├── looker.md │ │ │ ├── mongodb.md │ │ │ ├── mssql.md │ │ │ ├── mysql.md │ │ │ ├── neo4j.md │ │ │ ├── oceanbase.md │ │ │ ├── oracle.md │ │ │ ├── postgres.md │ │ │ ├── redis.md │ │ │ ├── spanner.md │ │ │ ├── sqlite.md │ │ │ ├── tidb.md │ │ │ ├── trino.md │ │ │ ├── valkey.md │ │ │ └── yugabytedb.md │ │ └── tools │ │ ├── _index.md │ │ ├── alloydb │ │ │ ├── _index.md │ │ │ ├── alloydb-create-cluster.md │ │ │ ├── alloydb-create-instance.md │ │ │ ├── alloydb-create-user.md │ │ │ ├── alloydb-get-cluster.md │ │ │ ├── alloydb-get-instance.md │ │ │ ├── alloydb-get-user.md │ │ │ ├── alloydb-list-clusters.md │ │ │ ├── alloydb-list-instances.md │ │ │ ├── alloydb-list-users.md │ │ │ └── alloydb-wait-for-operation.md │ │ ├── alloydbainl │ │ │ ├── _index.md │ │ │ └── alloydb-ai-nl.md │ │ ├── bigquery │ │ │ ├── _index.md │ │ │ ├── bigquery-analyze-contribution.md │ │ │ ├── bigquery-conversational-analytics.md │ │ │ ├── bigquery-execute-sql.md │ │ │ ├── bigquery-forecast.md │ │ │ ├── bigquery-get-dataset-info.md │ │ │ ├── bigquery-get-table-info.md │ │ │ ├── bigquery-list-dataset-ids.md │ │ │ ├── bigquery-list-table-ids.md │ │ │ ├── bigquery-search-catalog.md │ │ │ └── bigquery-sql.md │ │ ├── bigtable │ │ │ ├── _index.md │ │ │ └── bigtable-sql.md │ │ ├── cassandra │ │ │ ├── _index.md │ │ │ └── cassandra-cql.md │ │ ├── clickhouse │ │ │ ├── _index.md │ │ │ ├── clickhouse-execute-sql.md │ │ │ ├── clickhouse-list-databases.md │ │ │ ├── clickhouse-list-tables.md │ │ │ └── clickhouse-sql.md │ │ ├── cloudmonitoring │ │ │ ├── _index.md │ │ │ └── cloud-monitoring-query-prometheus.md │ │ ├── cloudsql │ │ │ ├── _index.md │ │ │ ├── cloudsqlcreatedatabase.md │ │ │ ├── cloudsqlcreateusers.md │ │ │ ├── cloudsqlgetinstances.md │ │ │ ├── cloudsqllistdatabases.md │ │ │ ├── cloudsqllistinstances.md │ │ │ ├── cloudsqlmssqlcreateinstance.md │ │ │ ├── cloudsqlmysqlcreateinstance.md │ │ │ ├── cloudsqlpgcreateinstances.md │ │ │ └── cloudsqlwaitforoperation.md │ │ ├── couchbase │ │ │ ├── _index.md │ │ │ └── couchbase-sql.md │ │ ├── dataform │ │ │ ├── _index.md │ │ │ └── dataform-compile-local.md │ │ ├── dataplex │ │ │ ├── _index.md │ │ │ ├── dataplex-lookup-entry.md │ │ │ ├── dataplex-search-aspect-types.md │ │ │ └── dataplex-search-entries.md │ │ ├── dgraph │ │ │ ├── _index.md │ │ │ └── dgraph-dql.md │ │ ├── firebird │ │ │ ├── _index.md │ │ │ ├── firebird-execute-sql.md │ │ │ └── firebird-sql.md │ │ ├── firestore │ │ │ ├── _index.md │ │ │ ├── firestore-add-documents.md │ │ │ ├── firestore-delete-documents.md │ │ │ ├── firestore-get-documents.md │ │ │ ├── firestore-get-rules.md │ │ │ ├── firestore-list-collections.md │ │ │ ├── firestore-query-collection.md │ │ │ ├── firestore-query.md │ │ │ ├── firestore-update-document.md │ │ │ └── firestore-validate-rules.md │ │ ├── http │ │ │ ├── _index.md │ │ │ └── http.md │ │ ├── looker │ │ │ ├── _index.md │ │ │ ├── looker-add-dashboard-element.md │ │ │ ├── looker-conversational-analytics.md │ │ │ ├── looker-get-dashboards.md │ │ │ ├── looker-get-dimensions.md │ │ │ ├── looker-get-explores.md │ │ │ ├── looker-get-filters.md │ │ │ ├── looker-get-looks.md │ │ │ ├── looker-get-measures.md │ │ │ ├── looker-get-models.md │ │ │ ├── looker-get-parameters.md │ │ │ ├── looker-health-analyze.md │ │ │ ├── looker-health-pulse.md │ │ │ ├── looker-health-vacuum.md │ │ │ ├── looker-make-dashboard.md │ │ │ ├── looker-make-look.md │ │ │ ├── looker-query-sql.md │ │ │ ├── looker-query-url.md │ │ │ ├── looker-query.md │ │ │ └── looker-run-look.md │ │ ├── mongodb │ │ │ ├── _index.md │ │ │ ├── mongodb-aggregate.md │ │ │ ├── mongodb-delete-many.md │ │ │ ├── mongodb-delete-one.md │ │ │ ├── mongodb-find-one.md │ │ │ ├── mongodb-find.md │ │ │ ├── mongodb-insert-many.md │ │ │ ├── mongodb-insert-one.md │ │ │ ├── mongodb-update-many.md │ │ │ └── mongodb-update-one.md │ │ ├── mssql │ │ │ ├── _index.md │ │ │ ├── mssql-execute-sql.md │ │ │ ├── mssql-list-tables.md │ │ │ └── mssql-sql.md │ │ ├── mysql │ │ │ ├── _index.md │ │ │ ├── mysql-execute-sql.md │ │ │ ├── mysql-list-active-queries.md │ │ │ ├── mysql-list-table-fragmentation.md │ │ │ ├── mysql-list-tables-missing-unique-indexes.md │ │ │ ├── mysql-list-tables.md │ │ │ └── mysql-sql.md │ │ ├── neo4j │ │ │ ├── _index.md │ │ │ ├── neo4j-cypher.md │ │ │ ├── neo4j-execute-cypher.md │ │ │ └── neo4j-schema.md │ │ ├── oceanbase │ │ │ ├── _index.md │ │ │ ├── oceanbase-execute-sql.md │ │ │ └── oceanbase-sql.md │ │ ├── oracle │ │ │ ├── _index.md │ │ │ ├── oracle-execute-sql.md │ │ │ └── oracle-sql.md │ │ ├── postgres │ │ │ ├── _index.md │ │ │ ├── postgres-execute-sql.md │ │ │ ├── postgres-list-active-queries.md │ │ │ ├── postgres-list-available-extensions.md │ │ │ ├── postgres-list-installed-extensions.md │ │ │ ├── postgres-list-tables.md │ │ │ └── postgres-sql.md │ │ ├── redis │ │ │ ├── _index.md │ │ │ └── redis.md │ │ ├── spanner │ │ │ ├── _index.md │ │ │ ├── spanner-execute-sql.md │ │ │ ├── spanner-list-tables.md │ │ │ └── spanner-sql.md │ │ ├── sqlite │ │ │ ├── _index.md │ │ │ ├── sqlite-execute-sql.md │ │ │ └── sqlite-sql.md │ │ ├── tidb │ │ │ ├── _index.md │ │ │ ├── tidb-execute-sql.md │ │ │ └── tidb-sql.md │ │ ├── trino │ │ │ ├── _index.md │ │ │ ├── trino-execute-sql.md │ │ │ └── trino-sql.md │ │ ├── utility │ │ │ ├── _index.md │ │ │ └── wait.md │ │ ├── valkey │ │ │ ├── _index.md │ │ │ └── valkey.md │ │ └── yuagbytedb │ │ ├── _index.md │ │ └── yugabytedb-sql.md │ ├── samples │ │ ├── _index.md │ │ ├── alloydb │ │ │ ├── _index.md │ │ │ ├── ai-nl │ │ │ │ ├── alloydb_ai_nl.ipynb │ │ │ │ └── index.md │ │ │ └── mcp_quickstart.md │ │ ├── bigquery │ │ │ ├── _index.md │ │ │ ├── colab_quickstart_bigquery.ipynb │ │ │ ├── local_quickstart.md │ │ │ └── mcp_quickstart │ │ │ ├── _index.md │ │ │ ├── inspector_tools.png │ │ │ └── inspector.png │ │ └── looker │ │ ├── _index.md │ │ ├── looker_gemini_oauth │ │ │ ├── _index.md │ │ │ ├── authenticated.png │ │ │ ├── authorize.png │ │ │ └── registration.png │ │ ├── looker_gemini.md │ │ └── looker_mcp_inspector │ │ ├── _index.md │ │ ├── inspector_tools.png │ │ └── inspector.png │ └── sdks │ ├── _index.md │ ├── go-sdk.md │ ├── js-sdk.md │ └── python-sdk.md ├── gemini-extension.json ├── go.mod ├── go.sum ├── internal │ ├── auth │ │ ├── auth.go │ │ └── google │ │ └── google.go │ ├── log │ │ ├── handler.go │ │ ├── log_test.go │ │ ├── log.go │ │ └── logger.go │ ├── prebuiltconfigs │ │ ├── prebuiltconfigs_test.go │ │ ├── prebuiltconfigs.go │ │ └── tools │ │ ├── alloydb-postgres-admin.yaml │ │ ├── alloydb-postgres-observability.yaml │ │ ├── alloydb-postgres.yaml │ │ ├── bigquery.yaml │ │ ├── clickhouse.yaml │ │ ├── cloud-sql-mssql-admin.yaml │ │ ├── cloud-sql-mssql-observability.yaml │ │ ├── cloud-sql-mssql.yaml │ │ ├── cloud-sql-mysql-admin.yaml │ │ ├── cloud-sql-mysql-observability.yaml │ │ ├── cloud-sql-mysql.yaml │ │ ├── cloud-sql-postgres-admin.yaml │ │ ├── cloud-sql-postgres-observability.yaml │ │ ├── cloud-sql-postgres.yaml │ │ ├── dataplex.yaml │ │ ├── firestore.yaml │ │ ├── looker-conversational-analytics.yaml │ │ ├── looker.yaml │ │ ├── mssql.yaml │ │ ├── mysql.yaml │ │ ├── neo4j.yaml │ │ ├── oceanbase.yaml │ │ ├── postgres.yaml │ │ ├── spanner-postgres.yaml │ │ ├── spanner.yaml │ │ └── sqlite.yaml │ ├── server │ │ ├── api_test.go │ │ ├── api.go │ │ ├── common_test.go │ │ ├── config.go │ │ ├── mcp │ │ │ ├── jsonrpc │ │ │ │ ├── jsonrpc_test.go │ │ │ │ └── jsonrpc.go │ │ │ ├── mcp.go │ │ │ ├── util │ │ │ │ └── lifecycle.go │ │ │ ├── v20241105 │ │ │ │ ├── method.go │ │ │ │ └── types.go │ │ │ ├── v20250326 │ │ │ │ ├── method.go │ │ │ │ └── types.go │ │ │ └── v20250618 │ │ │ ├── method.go │ │ │ └── types.go │ │ ├── mcp_test.go │ │ ├── mcp.go │ │ ├── server_test.go │ │ ├── server.go │ │ ├── static │ │ │ ├── assets │ │ │ │ └── mcptoolboxlogo.png │ │ │ ├── css │ │ │ │ └── style.css │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── auth.js │ │ │ │ ├── loadTools.js │ │ │ │ ├── mainContent.js │ │ │ │ ├── navbar.js │ │ │ │ ├── runTool.js │ │ │ │ ├── toolDisplay.js │ │ │ │ ├── tools.js │ │ │ │ └── toolsets.js │ │ │ ├── tools.html │ │ │ └── toolsets.html │ │ ├── web_test.go │ │ └── web.go │ ├── sources │ │ ├── alloydbadmin │ │ │ ├── alloydbadmin_test.go │ │ │ └── alloydbadmin.go │ │ ├── alloydbpg │ │ │ ├── alloydb_pg_test.go │ │ │ └── alloydb_pg.go │ │ ├── bigquery │ │ │ ├── bigquery_test.go │ │ │ └── bigquery.go │ │ ├── bigtable │ │ │ ├── bigtable_test.go │ │ │ └── bigtable.go │ │ ├── cassandra │ │ │ ├── cassandra_test.go │ │ │ └── cassandra.go │ │ ├── clickhouse │ │ │ ├── clickhouse_test.go │ │ │ └── clickhouse.go │ │ ├── cloudmonitoring │ │ │ ├── cloud_monitoring_test.go │ │ │ └── cloud_monitoring.go │ │ ├── cloudsqladmin │ │ │ ├── cloud_sql_admin_test.go │ │ │ └── cloud_sql_admin.go │ │ ├── cloudsqlmssql │ │ │ ├── cloud_sql_mssql_test.go │ │ │ └── cloud_sql_mssql.go │ │ ├── cloudsqlmysql │ │ │ ├── cloud_sql_mysql_test.go │ │ │ └── cloud_sql_mysql.go │ │ ├── cloudsqlpg │ │ │ ├── cloud_sql_pg_test.go │ │ │ └── cloud_sql_pg.go │ │ ├── couchbase │ │ │ ├── couchbase_test.go │ │ │ └── couchbase.go │ │ ├── dataplex │ │ │ ├── dataplex_test.go │ │ │ └── dataplex.go │ │ ├── dgraph │ │ │ ├── dgraph_test.go │ │ │ └── dgraph.go │ │ ├── dialect.go │ │ ├── firebird │ │ │ ├── firebird_test.go │ │ │ └── firebird.go │ │ ├── firestore │ │ │ ├── firestore_test.go │ │ │ └── firestore.go │ │ ├── http │ │ │ ├── http_test.go │ │ │ └── http.go │ │ ├── ip_type.go │ │ ├── looker │ │ │ ├── looker_test.go │ │ │ └── looker.go │ │ ├── mongodb │ │ │ ├── mongodb_test.go │ │ │ └── mongodb.go │ │ ├── mssql │ │ │ ├── mssql_test.go │ │ │ └── mssql.go │ │ ├── mysql │ │ │ ├── mysql_test.go │ │ │ └── mysql.go │ │ ├── neo4j │ │ │ ├── neo4j_test.go │ │ │ └── neo4j.go │ │ ├── oceanbase │ │ │ ├── oceanbase_test.go │ │ │ └── oceanbase.go │ │ ├── oracle │ │ │ └── oracle.go │ │ ├── postgres │ │ │ ├── postgres_test.go │ │ │ └── postgres.go │ │ ├── redis │ │ │ ├── redis_test.go │ │ │ └── redis.go │ │ ├── sources.go │ │ ├── spanner │ │ │ ├── spanner_test.go │ │ │ └── spanner.go │ │ ├── sqlite │ │ │ ├── sqlite_test.go │ │ │ └── sqlite.go │ │ ├── tidb │ │ │ ├── tidb_test.go │ │ │ └── tidb.go │ │ ├── trino │ │ │ ├── trino_test.go │ │ │ └── trino.go │ │ ├── util.go │ │ ├── valkey │ │ │ ├── valkey_test.go │ │ │ └── valkey.go │ │ └── yugabytedb │ │ ├── yugabytedb_test.go │ │ └── yugabytedb.go │ ├── telemetry │ │ ├── instrumentation.go │ │ └── telemetry.go │ ├── testutils │ │ └── testutils.go │ ├── tools │ │ ├── alloydb │ │ │ ├── alloydbcreatecluster │ │ │ │ ├── alloydbcreatecluster_test.go │ │ │ │ └── alloydbcreatecluster.go │ │ │ ├── alloydbcreateinstance │ │ │ │ ├── alloydbcreateinstance_test.go │ │ │ │ └── alloydbcreateinstance.go │ │ │ ├── alloydbcreateuser │ │ │ │ ├── alloydbcreateuser_test.go │ │ │ │ └── alloydbcreateuser.go │ │ │ ├── alloydbgetcluster │ │ │ │ ├── alloydbgetcluster_test.go │ │ │ │ └── alloydbgetcluster.go │ │ │ ├── alloydbgetinstance │ │ │ │ ├── alloydbgetinstance_test.go │ │ │ │ └── alloydbgetinstance.go │ │ │ ├── alloydbgetuser │ │ │ │ ├── alloydbgetuser_test.go │ │ │ │ └── alloydbgetuser.go │ │ │ ├── alloydblistclusters │ │ │ │ ├── alloydblistclusters_test.go │ │ │ │ └── alloydblistclusters.go │ │ │ ├── alloydblistinstances │ │ │ │ ├── alloydblistinstances_test.go │ │ │ │ └── alloydblistinstances.go │ │ │ ├── alloydblistusers │ │ │ │ ├── alloydblistusers_test.go │ │ │ │ └── alloydblistusers.go │ │ │ └── alloydbwaitforoperation │ │ │ ├── alloydbwaitforoperation_test.go │ │ │ └── alloydbwaitforoperation.go │ │ ├── alloydbainl │ │ │ ├── alloydbainl_test.go │ │ │ └── alloydbainl.go │ │ ├── bigquery │ │ │ ├── bigqueryanalyzecontribution │ │ │ │ ├── bigqueryanalyzecontribution_test.go │ │ │ │ └── bigqueryanalyzecontribution.go │ │ │ ├── bigquerycommon │ │ │ │ ├── table_name_parser_test.go │ │ │ │ ├── table_name_parser.go │ │ │ │ └── util.go │ │ │ ├── bigqueryconversationalanalytics │ │ │ │ ├── bigqueryconversationalanalytics_test.go │ │ │ │ └── bigqueryconversationalanalytics.go │ │ │ ├── bigqueryexecutesql │ │ │ │ ├── bigqueryexecutesql_test.go │ │ │ │ └── bigqueryexecutesql.go │ │ │ ├── bigqueryforecast │ │ │ │ ├── bigqueryforecast_test.go │ │ │ │ └── bigqueryforecast.go │ │ │ ├── bigquerygetdatasetinfo │ │ │ │ ├── bigquerygetdatasetinfo_test.go │ │ │ │ └── bigquerygetdatasetinfo.go │ │ │ ├── bigquerygettableinfo │ │ │ │ ├── bigquerygettableinfo_test.go │ │ │ │ └── bigquerygettableinfo.go │ │ │ ├── bigquerylistdatasetids │ │ │ │ ├── bigquerylistdatasetids_test.go │ │ │ │ └── bigquerylistdatasetids.go │ │ │ ├── bigquerylisttableids │ │ │ │ ├── bigquerylisttableids_test.go │ │ │ │ └── bigquerylisttableids.go │ │ │ ├── bigquerysearchcatalog │ │ │ │ ├── bigquerysearchcatalog_test.go │ │ │ │ └── bigquerysearchcatalog.go │ │ │ └── bigquerysql │ │ │ ├── bigquerysql_test.go │ │ │ └── bigquerysql.go │ │ ├── bigtable │ │ │ ├── bigtable_test.go │ │ │ └── bigtable.go │ │ ├── cassandra │ │ │ └── cassandracql │ │ │ ├── cassandracql_test.go │ │ │ └── cassandracql.go │ │ ├── clickhouse │ │ │ ├── clickhouseexecutesql │ │ │ │ ├── clickhouseexecutesql_test.go │ │ │ │ └── clickhouseexecutesql.go │ │ │ ├── clickhouselistdatabases │ │ │ │ ├── clickhouselistdatabases_test.go │ │ │ │ └── clickhouselistdatabases.go │ │ │ ├── clickhouselisttables │ │ │ │ ├── clickhouselisttables_test.go │ │ │ │ └── clickhouselisttables.go │ │ │ └── clickhousesql │ │ │ ├── clickhousesql_test.go │ │ │ └── clickhousesql.go │ │ ├── cloudmonitoring │ │ │ ├── cloudmonitoring_test.go │ │ │ └── cloudmonitoring.go │ │ ├── cloudsql │ │ │ ├── cloudsqlcreatedatabase │ │ │ │ ├── cloudsqlcreatedatabase_test.go │ │ │ │ └── cloudsqlcreatedatabase.go │ │ │ ├── cloudsqlcreateusers │ │ │ │ ├── cloudsqlcreateusers_test.go │ │ │ │ └── cloudsqlcreateusers.go │ │ │ ├── cloudsqlgetinstances │ │ │ │ ├── cloudsqlgetinstances_test.go │ │ │ │ └── cloudsqlgetinstances.go │ │ │ ├── cloudsqllistdatabases │ │ │ │ ├── cloudsqllistdatabases_test.go │ │ │ │ └── cloudsqllistdatabases.go │ │ │ ├── cloudsqllistinstances │ │ │ │ ├── cloudsqllistinstances_test.go │ │ │ │ └── cloudsqllistinstances.go │ │ │ └── cloudsqlwaitforoperation │ │ │ ├── cloudsqlwaitforoperation_test.go │ │ │ └── cloudsqlwaitforoperation.go │ │ ├── cloudsqlmssql │ │ │ └── cloudsqlmssqlcreateinstance │ │ │ ├── cloudsqlmssqlcreateinstance_test.go │ │ │ └── cloudsqlmssqlcreateinstance.go │ │ ├── cloudsqlmysql │ │ │ └── cloudsqlmysqlcreateinstance │ │ │ ├── cloudsqlmysqlcreateinstance_test.go │ │ │ └── cloudsqlmysqlcreateinstance.go │ │ ├── cloudsqlpg │ │ │ └── cloudsqlpgcreateinstances │ │ │ ├── cloudsqlpgcreateinstances_test.go │ │ │ └── cloudsqlpgcreateinstances.go │ │ ├── common_test.go │ │ ├── common.go │ │ ├── couchbase │ │ │ ├── couchbase_test.go │ │ │ └── couchbase.go │ │ ├── dataform │ │ │ └── dataformcompilelocal │ │ │ ├── dataformcompilelocal_test.go │ │ │ └── dataformcompilelocal.go │ │ ├── dataplex │ │ │ ├── dataplexlookupentry │ │ │ │ ├── dataplexlookupentry_test.go │ │ │ │ └── dataplexlookupentry.go │ │ │ ├── dataplexsearchaspecttypes │ │ │ │ ├── dataplexsearchaspecttypes_test.go │ │ │ │ └── dataplexsearchaspecttypes.go │ │ │ └── dataplexsearchentries │ │ │ ├── dataplexsearchentries_test.go │ │ │ └── dataplexsearchentries.go │ │ ├── dgraph │ │ │ ├── dgraph_test.go │ │ │ └── dgraph.go │ │ ├── firebird │ │ │ ├── firebirdexecutesql │ │ │ │ ├── firebirdexecutesql_test.go │ │ │ │ └── firebirdexecutesql.go │ │ │ └── firebirdsql │ │ │ ├── firebirdsql_test.go │ │ │ └── firebirdsql.go │ │ ├── firestore │ │ │ ├── firestoreadddocuments │ │ │ │ ├── firestoreadddocuments_test.go │ │ │ │ └── firestoreadddocuments.go │ │ │ ├── firestoredeletedocuments │ │ │ │ ├── firestoredeletedocuments_test.go │ │ │ │ └── firestoredeletedocuments.go │ │ │ ├── firestoregetdocuments │ │ │ │ ├── firestoregetdocuments_test.go │ │ │ │ └── firestoregetdocuments.go │ │ │ ├── firestoregetrules │ │ │ │ ├── firestoregetrules_test.go │ │ │ │ └── firestoregetrules.go │ │ │ ├── firestorelistcollections │ │ │ │ ├── firestorelistcollections_test.go │ │ │ │ └── firestorelistcollections.go │ │ │ ├── firestorequery │ │ │ │ ├── firestorequery_test.go │ │ │ │ └── firestorequery.go │ │ │ ├── firestorequerycollection │ │ │ │ ├── firestorequerycollection_test.go │ │ │ │ └── firestorequerycollection.go │ │ │ ├── firestoreupdatedocument │ │ │ │ ├── firestoreupdatedocument_test.go │ │ │ │ └── firestoreupdatedocument.go │ │ │ ├── firestorevalidaterules │ │ │ │ ├── firestorevalidaterules_test.go │ │ │ │ └── firestorevalidaterules.go │ │ │ └── util │ │ │ ├── converter_test.go │ │ │ ├── converter.go │ │ │ ├── validator_test.go │ │ │ └── validator.go │ │ ├── http │ │ │ ├── http_test.go │ │ │ └── http.go │ │ ├── http_method.go │ │ ├── looker │ │ │ ├── lookeradddashboardelement │ │ │ │ ├── lookeradddashboardelement_test.go │ │ │ │ └── lookeradddashboardelement.go │ │ │ ├── lookercommon │ │ │ │ ├── lookercommon_test.go │ │ │ │ └── lookercommon.go │ │ │ ├── lookerconversationalanalytics │ │ │ │ ├── lookerconversationalanalytics_test.go │ │ │ │ └── lookerconversationalanalytics.go │ │ │ ├── lookergetdashboards │ │ │ │ ├── lookergetdashboards_test.go │ │ │ │ └── lookergetdashboards.go │ │ │ ├── lookergetdimensions │ │ │ │ ├── lookergetdimensions_test.go │ │ │ │ └── lookergetdimensions.go │ │ │ ├── lookergetexplores │ │ │ │ ├── lookergetexplores_test.go │ │ │ │ └── lookergetexplores.go │ │ │ ├── lookergetfilters │ │ │ │ ├── lookergetfilters_test.go │ │ │ │ └── lookergetfilters.go │ │ │ ├── lookergetlooks │ │ │ │ ├── lookergetlooks_test.go │ │ │ │ └── lookergetlooks.go │ │ │ ├── lookergetmeasures │ │ │ │ ├── lookergetmeasures_test.go │ │ │ │ └── lookergetmeasures.go │ │ │ ├── lookergetmodels │ │ │ │ ├── lookergetmodels_test.go │ │ │ │ └── lookergetmodels.go │ │ │ ├── lookergetparameters │ │ │ │ ├── lookergetparameters_test.go │ │ │ │ └── lookergetparameters.go │ │ │ ├── lookerhealthanalyze │ │ │ │ ├── lookerhealthanalyze_test.go │ │ │ │ └── lookerhealthanalyze.go │ │ │ ├── lookerhealthpulse │ │ │ │ ├── lookerhealthpulse_test.go │ │ │ │ └── lookerhealthpulse.go │ │ │ ├── lookerhealthvacuum │ │ │ │ ├── lookerhealthvacuum_test.go │ │ │ │ └── lookerhealthvacuum.go │ │ │ ├── lookermakedashboard │ │ │ │ ├── lookermakedashboard_test.go │ │ │ │ └── lookermakedashboard.go │ │ │ ├── lookermakelook │ │ │ │ ├── lookermakelook_test.go │ │ │ │ └── lookermakelook.go │ │ │ ├── lookerquery │ │ │ │ ├── lookerquery_test.go │ │ │ │ └── lookerquery.go │ │ │ ├── lookerquerysql │ │ │ │ ├── lookerquerysql_test.go │ │ │ │ └── lookerquerysql.go │ │ │ ├── lookerqueryurl │ │ │ │ ├── lookerqueryurl_test.go │ │ │ │ └── lookerqueryurl.go │ │ │ └── lookerrunlook │ │ │ ├── lookerrunlook_test.go │ │ │ └── lookerrunlook.go │ │ ├── mongodb │ │ │ ├── mongodbaggregate │ │ │ │ ├── mongodbaggregate_test.go │ │ │ │ └── mongodbaggregate.go │ │ │ ├── mongodbdeletemany │ │ │ │ ├── mongodbdeletemany_test.go │ │ │ │ └── mongodbdeletemany.go │ │ │ ├── mongodbdeleteone │ │ │ │ ├── mongodbdeleteone_test.go │ │ │ │ └── mongodbdeleteone.go │ │ │ ├── mongodbfind │ │ │ │ ├── mongodbfind_test.go │ │ │ │ └── mongodbfind.go │ │ │ ├── mongodbfindone │ │ │ │ ├── mongodbfindone_test.go │ │ │ │ └── mongodbfindone.go │ │ │ ├── mongodbinsertmany │ │ │ │ ├── mongodbinsertmany_test.go │ │ │ │ └── mongodbinsertmany.go │ │ │ ├── mongodbinsertone │ │ │ │ ├── mongodbinsertone_test.go │ │ │ │ └── mongodbinsertone.go │ │ │ ├── mongodbupdatemany │ │ │ │ ├── mongodbupdatemany_test.go │ │ │ │ └── mongodbupdatemany.go │ │ │ └── mongodbupdateone │ │ │ ├── mongodbupdateone_test.go │ │ │ └── mongodbupdateone.go │ │ ├── mssql │ │ │ ├── mssqlexecutesql │ │ │ │ ├── mssqlexecutesql_test.go │ │ │ │ └── mssqlexecutesql.go │ │ │ ├── mssqllisttables │ │ │ │ ├── mssqllisttables_test.go │ │ │ │ └── mssqllisttables.go │ │ │ └── mssqlsql │ │ │ ├── mssqlsql_test.go │ │ │ └── mssqlsql.go │ │ ├── mysql │ │ │ ├── mysqlcommon │ │ │ │ └── mysqlcommon.go │ │ │ ├── mysqlexecutesql │ │ │ │ ├── mysqlexecutesql_test.go │ │ │ │ └── mysqlexecutesql.go │ │ │ ├── mysqllistactivequeries │ │ │ │ ├── mysqllistactivequeries_test.go │ │ │ │ └── mysqllistactivequeries.go │ │ │ ├── mysqllisttablefragmentation │ │ │ │ ├── mysqllisttablefragmentation_test.go │ │ │ │ └── mysqllisttablefragmentation.go │ │ │ ├── mysqllisttables │ │ │ │ ├── mysqllisttables_test.go │ │ │ │ └── mysqllisttables.go │ │ │ ├── mysqllisttablesmissinguniqueindexes │ │ │ │ ├── mysqllisttablesmissinguniqueindexes_test.go │ │ │ │ └── mysqllisttablesmissinguniqueindexes.go │ │ │ └── mysqlsql │ │ │ ├── mysqlsql_test.go │ │ │ └── mysqlsql.go │ │ ├── neo4j │ │ │ ├── neo4jcypher │ │ │ │ ├── neo4jcypher_test.go │ │ │ │ └── neo4jcypher.go │ │ │ ├── neo4jexecutecypher │ │ │ │ ├── classifier │ │ │ │ │ ├── classifier_test.go │ │ │ │ │ └── classifier.go │ │ │ │ ├── neo4jexecutecypher_test.go │ │ │ │ └── neo4jexecutecypher.go │ │ │ └── neo4jschema │ │ │ ├── cache │ │ │ │ ├── cache_test.go │ │ │ │ └── cache.go │ │ │ ├── helpers │ │ │ │ ├── helpers_test.go │ │ │ │ └── helpers.go │ │ │ ├── neo4jschema_test.go │ │ │ ├── neo4jschema.go │ │ │ └── types │ │ │ └── types.go │ │ ├── oceanbase │ │ │ ├── oceanbaseexecutesql │ │ │ │ ├── oceanbaseexecutesql_test.go │ │ │ │ └── oceanbaseexecutesql.go │ │ │ └── oceanbasesql │ │ │ ├── oceanbasesql_test.go │ │ │ └── oceanbasesql.go │ │ ├── oracle │ │ │ ├── oracleexecutesql │ │ │ │ └── oracleexecutesql.go │ │ │ └── oraclesql │ │ │ └── oraclesql.go │ │ ├── parameters_test.go │ │ ├── parameters.go │ │ ├── postgres │ │ │ ├── postgresexecutesql │ │ │ │ ├── postgresexecutesql_test.go │ │ │ │ └── postgresexecutesql.go │ │ │ ├── postgreslistactivequeries │ │ │ │ ├── postgreslistactivequeries_test.go │ │ │ │ └── postgreslistactivequeries.go │ │ │ ├── postgreslistavailableextensions │ │ │ │ ├── postgreslistavailableextensions_test.go │ │ │ │ └── postgreslistavailableextensions.go │ │ │ ├── postgreslistinstalledextensions │ │ │ │ ├── postgreslistinstalledextensions_test.go │ │ │ │ └── postgreslistinstalledextensions.go │ │ │ ├── postgreslisttables │ │ │ │ ├── postgreslisttables_test.go │ │ │ │ └── postgreslisttables.go │ │ │ └── postgressql │ │ │ ├── postgressql_test.go │ │ │ └── postgressql.go │ │ ├── redis │ │ │ ├── redis_test.go │ │ │ └── redis.go │ │ ├── spanner │ │ │ ├── spannerexecutesql │ │ │ │ ├── spannerexecutesql_test.go │ │ │ │ └── spannerexecutesql.go │ │ │ ├── spannerlisttables │ │ │ │ ├── spannerlisttables_test.go │ │ │ │ └── spannerlisttables.go │ │ │ └── spannersql │ │ │ ├── spanner_test.go │ │ │ └── spannersql.go │ │ ├── sqlite │ │ │ ├── sqliteexecutesql │ │ │ │ ├── sqliteexecutesql_test.go │ │ │ │ └── sqliteexecutesql.go │ │ │ └── sqlitesql │ │ │ ├── sqlitesql_test.go │ │ │ └── sqlitesql.go │ │ ├── tidb │ │ │ ├── tidbexecutesql │ │ │ │ ├── tidbexecutesql_test.go │ │ │ │ └── tidbexecutesql.go │ │ │ └── tidbsql │ │ │ ├── tidbsql_test.go │ │ │ └── tidbsql.go │ │ ├── tools_test.go │ │ ├── tools.go │ │ ├── toolsets.go │ │ ├── trino │ │ │ ├── trinoexecutesql │ │ │ │ ├── trinoexecutesql_test.go │ │ │ │ └── trinoexecutesql.go │ │ │ └── trinosql │ │ │ ├── trinosql_test.go │ │ │ └── trinosql.go │ │ ├── utility │ │ │ └── wait │ │ │ ├── wait_test.go │ │ │ └── wait.go │ │ ├── valkey │ │ │ ├── valkey_test.go │ │ │ └── valkey.go │ │ └── yugabytedbsql │ │ ├── yugabytedbsql_test.go │ │ └── yugabytedbsql.go │ └── util │ └── util.go ├── LICENSE ├── logo.png ├── main.go ├── MCP-TOOLBOX-EXTENSION.md ├── README.md └── tests ├── alloydb │ ├── alloydb_integration_test.go │ └── alloydb_wait_for_operation_test.go ├── alloydbainl │ └── alloydb_ai_nl_integration_test.go ├── alloydbpg │ └── alloydb_pg_integration_test.go ├── auth.go ├── bigquery │ └── bigquery_integration_test.go ├── bigtable │ └── bigtable_integration_test.go ├── cassandra │ └── cassandra_integration_test.go ├── clickhouse │ └── clickhouse_integration_test.go ├── cloudmonitoring │ └── cloud_monitoring_integration_test.go ├── cloudsql │ ├── cloud_sql_create_database_test.go │ ├── cloud_sql_create_users_test.go │ ├── cloud_sql_get_instances_test.go │ ├── cloud_sql_list_databases_test.go │ ├── cloudsql_list_instances_test.go │ └── cloudsql_wait_for_operation_test.go ├── cloudsqlmssql │ ├── cloud_sql_mssql_create_instance_integration_test.go │ └── cloud_sql_mssql_integration_test.go ├── cloudsqlmysql │ ├── cloud_sql_mysql_create_instance_integration_test.go │ └── cloud_sql_mysql_integration_test.go ├── cloudsqlpg │ ├── cloud_sql_pg_create_instances_test.go │ └── cloud_sql_pg_integration_test.go ├── common.go ├── couchbase │ └── couchbase_integration_test.go ├── dataform │ └── dataform_integration_test.go ├── dataplex │ └── dataplex_integration_test.go ├── dgraph │ └── dgraph_integration_test.go ├── firebird │ └── firebird_integration_test.go ├── firestore │ └── firestore_integration_test.go ├── http │ └── http_integration_test.go ├── looker │ └── looker_integration_test.go ├── mongodb │ └── mongodb_integration_test.go ├── mssql │ └── mssql_integration_test.go ├── mysql │ └── mysql_integration_test.go ├── neo4j │ └── neo4j_integration_test.go ├── oceanbase │ └── oceanbase_integration_test.go ├── option.go ├── oracle │ └── oracle_integration_test.go ├── postgres │ └── postgres_integration_test.go ├── redis │ └── redis_test.go ├── server.go ├── source.go ├── spanner │ └── spanner_integration_test.go ├── sqlite │ └── sqlite_integration_test.go ├── tidb │ └── tidb_integration_test.go ├── tool.go ├── trino │ └── trino_integration_test.go ├── utility │ └── wait_integration_test.go ├── valkey │ └── valkey_test.go └── yugabytedb └── yugabytedb_integration_test.go ``` # Files -------------------------------------------------------------------------------- /internal/tools/dataplex/dataplexlookupentry/dataplexlookupentry_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dataplexlookupentry_test 16 | 17 | import ( 18 | "testing" 19 | 20 | yaml "github.com/goccy/go-yaml" 21 | "github.com/google/go-cmp/cmp" 22 | "github.com/googleapis/genai-toolbox/internal/server" 23 | "github.com/googleapis/genai-toolbox/internal/testutils" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | "github.com/googleapis/genai-toolbox/internal/tools/dataplex/dataplexlookupentry" 26 | ) 27 | 28 | func TestParseFromYamlDataplexLookupEntry(t *testing.T) { 29 | ctx, err := testutils.ContextWithNewLogger() 30 | if err != nil { 31 | t.Fatalf("unexpected error: %s", err) 32 | } 33 | tcs := []struct { 34 | desc string 35 | in string 36 | want server.ToolConfigs 37 | }{ 38 | { 39 | desc: "basic example", 40 | in: ` 41 | tools: 42 | example_tool: 43 | kind: dataplex-lookup-entry 44 | source: my-instance 45 | description: some description 46 | `, 47 | want: server.ToolConfigs{ 48 | "example_tool": dataplexlookupentry.Config{ 49 | Name: "example_tool", 50 | Kind: "dataplex-lookup-entry", 51 | Source: "my-instance", 52 | Description: "some description", 53 | AuthRequired: []string{}, 54 | }, 55 | }, 56 | }, 57 | { 58 | desc: "advanced example", 59 | in: ` 60 | tools: 61 | example_tool: 62 | kind: dataplex-lookup-entry 63 | source: my-instance 64 | description: some description 65 | parameters: 66 | - name: name 67 | type: string 68 | description: some name description 69 | - name: view 70 | type: string 71 | description: some view description 72 | - name: aspectTypes 73 | type: array 74 | description: some aspect types description 75 | default: [] 76 | items: 77 | name: aspectType 78 | type: string 79 | description: some aspect type description 80 | - name: entry 81 | type: string 82 | description: some entry description 83 | `, 84 | want: server.ToolConfigs{ 85 | "example_tool": dataplexlookupentry.Config{ 86 | Name: "example_tool", 87 | Kind: "dataplex-lookup-entry", 88 | Source: "my-instance", 89 | Description: "some description", 90 | AuthRequired: []string{}, 91 | Parameters: []tools.Parameter{ 92 | tools.NewStringParameter("name", "some name description"), 93 | tools.NewStringParameter("view", "some view description"), 94 | tools.NewArrayParameterWithDefault("aspectTypes", []any{}, "some aspect types description", tools.NewStringParameter("aspectType", "some aspect type description")), 95 | tools.NewStringParameter("entry", "some entry description"), 96 | }, 97 | }, 98 | }, 99 | }, 100 | } 101 | for _, tc := range tcs { 102 | t.Run(tc.desc, func(t *testing.T) { 103 | got := struct { 104 | Tools server.ToolConfigs `yaml:"tools"` 105 | }{} 106 | // Parse contents 107 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 108 | if err != nil { 109 | t.Fatalf("unable to unmarshal: %s", err) 110 | } 111 | if diff := cmp.Diff(tc.want, got.Tools); diff != "" { 112 | t.Fatalf("incorrect parse: diff %v", diff) 113 | } 114 | }) 115 | } 116 | 117 | } 118 | ``` -------------------------------------------------------------------------------- /internal/sources/cloudmonitoring/cloud_monitoring_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudmonitoring_test 16 | 17 | import ( 18 | "testing" 19 | 20 | yaml "github.com/goccy/go-yaml" 21 | "github.com/google/go-cmp/cmp" 22 | "github.com/googleapis/genai-toolbox/internal/server" 23 | "github.com/googleapis/genai-toolbox/internal/sources" 24 | "github.com/googleapis/genai-toolbox/internal/sources/cloudmonitoring" 25 | "github.com/googleapis/genai-toolbox/internal/testutils" 26 | ) 27 | 28 | func TestParseFromYamlCloudMonitoring(t *testing.T) { 29 | t.Parallel() 30 | tcs := []struct { 31 | desc string 32 | in string 33 | want server.SourceConfigs 34 | }{ 35 | { 36 | desc: "basic example", 37 | in: ` 38 | sources: 39 | my-cloud-monitoring-instance: 40 | kind: cloud-monitoring 41 | `, 42 | want: map[string]sources.SourceConfig{ 43 | "my-cloud-monitoring-instance": cloudmonitoring.Config{ 44 | Name: "my-cloud-monitoring-instance", 45 | Kind: cloudmonitoring.SourceKind, 46 | UseClientOAuth: false, 47 | }, 48 | }, 49 | }, 50 | { 51 | desc: "use client auth example", 52 | in: ` 53 | sources: 54 | my-cloud-monitoring-instance: 55 | kind: cloud-monitoring 56 | useClientOAuth: true 57 | `, 58 | want: map[string]sources.SourceConfig{ 59 | "my-cloud-monitoring-instance": cloudmonitoring.Config{ 60 | Name: "my-cloud-monitoring-instance", 61 | Kind: cloudmonitoring.SourceKind, 62 | UseClientOAuth: true, 63 | }, 64 | }, 65 | }, 66 | } 67 | for _, tc := range tcs { 68 | tc := tc 69 | t.Run(tc.desc, func(t *testing.T) { 70 | t.Parallel() 71 | got := struct { 72 | Sources server.SourceConfigs `yaml:"sources"` 73 | }{} 74 | // Parse contents 75 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 76 | if err != nil { 77 | t.Fatalf("unable to unmarshal: %s", err) 78 | } 79 | if !cmp.Equal(tc.want, got.Sources) { 80 | t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources) 81 | } 82 | }) 83 | } 84 | } 85 | 86 | func TestFailParseFromYaml(t *testing.T) { 87 | t.Parallel() 88 | tcs := []struct { 89 | desc string 90 | in string 91 | err string 92 | }{ 93 | { 94 | desc: "extra field", 95 | in: ` 96 | sources: 97 | my-cloud-monitoring-instance: 98 | kind: cloud-monitoring 99 | project: test-project 100 | `, 101 | err: `unable to parse source "my-cloud-monitoring-instance" as "cloud-monitoring": [2:1] unknown field "project" 102 | 1 | kind: cloud-monitoring 103 | > 2 | project: test-project 104 | ^ 105 | `, 106 | }, 107 | { 108 | desc: "missing required field", 109 | in: ` 110 | sources: 111 | my-cloud-monitoring-instance: 112 | useClientOAuth: true 113 | `, 114 | err: "missing 'kind' field for source \"my-cloud-monitoring-instance\"", 115 | }, 116 | } 117 | for _, tc := range tcs { 118 | tc := tc 119 | t.Run(tc.desc, func(t *testing.T) { 120 | t.Parallel() 121 | got := struct { 122 | Sources server.SourceConfigs `yaml:"sources"` 123 | }{} 124 | // Parse contents 125 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 126 | if err == nil { 127 | t.Fatalf("expect parsing to fail") 128 | } 129 | errStr := err.Error() 130 | if errStr != tc.err { 131 | t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) 132 | } 133 | }) 134 | } 135 | } 136 | ``` -------------------------------------------------------------------------------- /internal/sources/dgraph/dgraph_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dgraph_test 16 | 17 | import ( 18 | "testing" 19 | 20 | yaml "github.com/goccy/go-yaml" 21 | "github.com/google/go-cmp/cmp" 22 | "github.com/googleapis/genai-toolbox/internal/server" 23 | "github.com/googleapis/genai-toolbox/internal/sources/dgraph" 24 | "github.com/googleapis/genai-toolbox/internal/testutils" 25 | ) 26 | 27 | func TestParseFromYamlDgraph(t *testing.T) { 28 | tcs := []struct { 29 | desc string 30 | in string 31 | want server.SourceConfigs 32 | }{ 33 | { 34 | desc: "basic example", 35 | in: ` 36 | sources: 37 | my-dgraph-instance: 38 | kind: dgraph 39 | dgraphUrl: https://localhost:8080 40 | apiKey: abc123 41 | password: pass@123 42 | namespace: 0 43 | user: user123 44 | `, 45 | want: server.SourceConfigs{ 46 | "my-dgraph-instance": dgraph.Config{ 47 | Name: "my-dgraph-instance", 48 | Kind: dgraph.SourceKind, 49 | DgraphUrl: "https://localhost:8080", 50 | ApiKey: "abc123", 51 | Password: "pass@123", 52 | Namespace: 0, 53 | User: "user123", 54 | }, 55 | }, 56 | }, 57 | { 58 | desc: "basic example minimal field", 59 | in: ` 60 | sources: 61 | my-dgraph-instance: 62 | kind: dgraph 63 | dgraphUrl: https://localhost:8080 64 | `, 65 | want: server.SourceConfigs{ 66 | "my-dgraph-instance": dgraph.Config{ 67 | Name: "my-dgraph-instance", 68 | Kind: dgraph.SourceKind, 69 | DgraphUrl: "https://localhost:8080", 70 | }, 71 | }, 72 | }, 73 | } 74 | 75 | for _, tc := range tcs { 76 | t.Run(tc.desc, func(t *testing.T) { 77 | got := struct { 78 | Sources server.SourceConfigs `yaml:"sources"` 79 | }{} 80 | // Parse contents 81 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 82 | if err != nil { 83 | t.Fatalf("unable to unmarshal: %s", err) 84 | } 85 | 86 | if diff := cmp.Diff(tc.want, got.Sources); diff != "" { 87 | t.Fatalf("incorrect parse: diff %v", diff) 88 | } 89 | }) 90 | } 91 | 92 | } 93 | 94 | func TestFailParseFromYaml(t *testing.T) { 95 | tcs := []struct { 96 | desc string 97 | in string 98 | err string 99 | }{ 100 | { 101 | desc: "extra field", 102 | in: ` 103 | sources: 104 | my-dgraph-instance: 105 | kind: dgraph 106 | dgraphUrl: https://localhost:8080 107 | foo: bar 108 | `, 109 | 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", 110 | }, 111 | { 112 | desc: "missing required field", 113 | in: ` 114 | sources: 115 | my-dgraph-instance: 116 | kind: dgraph 117 | `, 118 | err: "unable to parse source \"my-dgraph-instance\" as \"dgraph\": Key: 'Config.DgraphUrl' Error:Field validation for 'DgraphUrl' failed on the 'required' tag", 119 | }, 120 | } 121 | for _, tc := range tcs { 122 | t.Run(tc.desc, func(t *testing.T) { 123 | got := struct { 124 | Sources server.SourceConfigs `yaml:"sources"` 125 | }{} 126 | // Parse contents 127 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 128 | if err == nil { 129 | t.Fatalf("expect parsing to fail") 130 | } 131 | errStr := err.Error() 132 | if errStr != tc.err { 133 | t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) 134 | } 135 | }) 136 | } 137 | } 138 | ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/spanner.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "Spanner" 3 | type: docs 4 | weight: 1 5 | description: > 6 | Spanner is a fully managed database service from Google Cloud that combines 7 | relational, key-value, graph, and search capabilities. 8 | 9 | --- 10 | 11 | # Spanner Source 12 | 13 | [Spanner][spanner-docs] is a fully managed, mission-critical database service 14 | that brings together relational, graph, key-value, and search. It offers 15 | transactional consistency at global scale, automatic, synchronous replication 16 | for high availability, and support for two SQL dialects: GoogleSQL (ANSI 2011 17 | with extensions) and PostgreSQL. 18 | 19 | If you are new to Spanner, you can try to [create and query a database using 20 | the Google Cloud console][spanner-quickstart]. 21 | 22 | [spanner-docs]: https://cloud.google.com/spanner/docs 23 | [spanner-quickstart]: 24 | https://cloud.google.com/spanner/docs/create-query-database-console 25 | 26 | ## Available Tools 27 | 28 | - [`spanner-sql`](../tools/spanner/spanner-sql.md) 29 | Execute SQL on Google Cloud Spanner. 30 | 31 | - [`spanner-execute-sql`](../tools/spanner/spanner-execute-sql.md) 32 | Run structured and parameterized queries on Spanner. 33 | 34 | ### Pre-built Configurations 35 | 36 | - [Spanner using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/spanner_mcp/) 37 | Connect your IDE to Spanner using Toolbox. 38 | 39 | ## Requirements 40 | 41 | ### IAM Permissions 42 | 43 | Spanner uses [Identity and Access Management (IAM)][iam-overview] to control 44 | user and group access to Spanner resources at the project, Spanner instance, and 45 | Spanner database levels. Toolbox will use your [Application Default Credentials 46 | (ADC)][adc] to authorize and authenticate when interacting with Spanner. 47 | 48 | In addition to [setting the ADC for your server][set-adc], you need to ensure 49 | the IAM identity has been given the correct IAM permissions for the query 50 | provided. See [Apply IAM roles][grant-permissions] for more information on 51 | applying IAM permissions and roles to an identity. 52 | 53 | [iam-overview]: https://cloud.google.com/spanner/docs/iam 54 | [adc]: https://cloud.google.com/docs/authentication#adc 55 | [set-adc]: https://cloud.google.com/docs/authentication/provide-credentials-adc 56 | [grant-permissions]: https://cloud.google.com/spanner/docs/grant-permissions 57 | 58 | ## Example 59 | 60 | ```yaml 61 | sources: 62 | my-spanner-source: 63 | kind: "spanner" 64 | project: "my-project-id" 65 | instance: "my-instance" 66 | database: "my_db" 67 | # dialect: "googlesql" 68 | ``` 69 | 70 | ## Reference 71 | 72 | | **field** | **type** | **required** | **description** | 73 | |-----------|:--------:|:------------:|---------------------------------------------------------------------------------------------------------------------| 74 | | kind | string | true | Must be "spanner". | 75 | | project | string | true | Id of the GCP project that the cluster was created in (e.g. "my-project-id"). | 76 | | instance | string | true | Name of the Spanner instance. | 77 | | database | string | true | Name of the database on the Spanner instance | 78 | | dialect | string | false | Name of the dialect type of the Spanner database, must be either `googlesql` or `postgresql`. Default: `googlesql`. | 79 | ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/firestore.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "Firestore" 3 | type: docs 4 | weight: 1 5 | description: > 6 | Firestore is a NoSQL document database built for automatic scaling, high performance, and ease of application development. It's a fully managed, serverless database that supports mobile, web, and server development. 7 | 8 | --- 9 | 10 | # Firestore Source 11 | 12 | [Firestore][firestore-docs] is a NoSQL document database built for automatic 13 | scaling, high performance, and ease of application development. While the 14 | Firestore interface has many of the same features as traditional databases, 15 | as a NoSQL database it differs from them in the way it describes relationships 16 | between data objects. 17 | 18 | If you are new to Firestore, you can [create a database and learn the 19 | basics][firestore-quickstart]. 20 | 21 | [firestore-docs]: https://cloud.google.com/firestore/docs 22 | [firestore-quickstart]: https://cloud.google.com/firestore/docs/quickstart-servers 23 | 24 | ## Requirements 25 | 26 | ### IAM Permissions 27 | 28 | Firestore uses [Identity and Access Management (IAM)][iam-overview] to control 29 | user and group access to Firestore resources. Toolbox will use your [Application 30 | Default Credentials (ADC)][adc] to authorize and authenticate when interacting 31 | with [Firestore][firestore-docs]. 32 | 33 | In addition to [setting the ADC for your server][set-adc], you need to ensure 34 | the IAM identity has been given the correct IAM permissions for accessing 35 | Firestore. Common roles include: 36 | 37 | - `roles/datastore.user` - Read and write access to Firestore 38 | - `roles/datastore.viewer` - Read-only access to Firestore 39 | - `roles/firebaserules.admin` - Full management of Firebase Security Rules for 40 | Firestore. This role is required for operations that involve creating, 41 | updating, or managing Firestore security rules (see [Firebase Security Rules 42 | roles][firebaserules-roles]) 43 | 44 | See [Firestore access control][firestore-iam] for more information on 45 | applying IAM permissions and roles to an identity. 46 | 47 | [iam-overview]: https://cloud.google.com/firestore/docs/security/iam 48 | [adc]: https://cloud.google.com/docs/authentication#adc 49 | [set-adc]: https://cloud.google.com/docs/authentication/provide-credentials-adc 50 | [firestore-iam]: https://cloud.google.com/firestore/docs/security/iam 51 | [firebaserules-roles]: 52 | https://cloud.google.com/iam/docs/roles-permissions/firebaserules 53 | 54 | ### Database Selection 55 | 56 | Firestore allows you to create multiple databases within a single project. Each 57 | database is isolated from the others and has its own set of documents and 58 | collections. If you don't specify a database in your configuration, the default 59 | database named `(default)` will be used. 60 | 61 | ## Example 62 | 63 | ```yaml 64 | sources: 65 | my-firestore-source: 66 | kind: "firestore" 67 | project: "my-project-id" 68 | # database: "my-database" # Optional, defaults to "(default)" 69 | ``` 70 | 71 | ## Reference 72 | 73 | | **field** | **type** | **required** | **description** | 74 | |-----------|:--------:|:------------:|----------------------------------------------------------------------------------------------------------| 75 | | kind | string | true | Must be "firestore". | 76 | | project | string | true | Id of the GCP project that contains the Firestore database (e.g. "my-project-id"). | 77 | | database | string | false | Name of the Firestore database to connect to. Defaults to "(default)" if not specified. | 78 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/bigquery/bigquery-forecast.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "bigquery-forecast" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "bigquery-forecast" tool forecasts time series data in BigQuery. 7 | aliases: 8 | - /resources/tools/bigquery-forecast 9 | --- 10 | 11 | ## About 12 | 13 | A `bigquery-forecast` tool forecasts time series data in BigQuery. 14 | It's compatible with the following sources: 15 | 16 | - [bigquery](../../sources/bigquery.md) 17 | 18 | `bigquery-forecast` constructs and executes a `SELECT * FROM AI.FORECAST(...)` 19 | query based on the provided parameters: 20 | 21 | - **history_data** (string, required): This specifies the source of the 22 | historical time series data. It can be either a fully qualified BigQuery table 23 | ID (e.g., my-project.my_dataset.my_table) or a SQL query that returns the 24 | data. 25 | - **timestamp_col** (string, required): The name of the column in your 26 | history_data that contains the timestamps. 27 | - **data_col** (string, required): The name of the column in your history_data 28 | that contains the numeric values to be forecasted. 29 | - **id_cols** (array of strings, optional): If you are forecasting multiple time 30 | series at once (e.g., sales for different products), this parameter takes an 31 | array of column names that uniquely identify each series. It defaults to an 32 | empty array if not provided. 33 | - **horizon** (integer, optional): The number of future time steps you want to 34 | predict. It defaults to 10 if not specified. 35 | 36 | The behavior of this tool is influenced by the `writeMode` setting on its `bigquery` source: 37 | 38 | - **`allowed` (default) and `blocked`:** These modes do not impose any special restrictions on the `bigquery-forecast` tool. 39 | - **`protected`:** This mode enables session-based execution. The tool will operate within the same BigQuery session as other 40 | tools using the same source. This allows the `history_data` parameter to be a query that references temporary resources (e.g., 41 | `TEMP` tables) created within that session. 42 | 43 | The tool's behavior is also influenced by the `allowedDatasets` restriction on the `bigquery` source: 44 | 45 | - **Without `allowedDatasets` restriction:** The tool can use any table or query for the `history_data` parameter. 46 | - **With `allowedDatasets` restriction:** The tool verifies that the `history_data` parameter only accesses tables within the allowed datasets. 47 | - If `history_data` is a table ID, the tool checks if the table's dataset is in the allowed list. 48 | - If `history_data` is a query, the tool performs a dry run to analyze the query and rejects it if it accesses any table outside the allowed list. 49 | 50 | ## Example 51 | 52 | ```yaml 53 | tools: 54 | forecast_tool: 55 | kind: bigquery-forecast 56 | source: my-bigquery-source 57 | description: Use this tool to forecast time series data in BigQuery. 58 | ``` 59 | 60 | ## Sample Prompt 61 | You can use the following sample prompts to call this tool: 62 | 63 | - Can you forecast the history time series data in bigquery table `bqml_tutorial.google_analytic`? Use project_id `myproject`. 64 | - What are the future `total_visits` in bigquery table `bqml_tutorial.google_analytic`? 65 | 66 | 67 | ## Reference 68 | 69 | | **field** | **type** | **required** | **description** | 70 | |-------------|:--------:|:------------:|---------------------------------------------------------| 71 | | kind | string | true | Must be "bigquery-forecast". | 72 | | source | string | true | Name of the source the forecast tool should execute on. | 73 | | description | string | true | Description of the tool that is passed to the LLM. | 74 | ``` -------------------------------------------------------------------------------- /internal/prebuiltconfigs/tools/cloud-sql-mysql.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | sources: 16 | cloud-sql-mysql-source: 17 | kind: cloud-sql-mysql 18 | project: ${CLOUD_SQL_MYSQL_PROJECT} 19 | region: ${CLOUD_SQL_MYSQL_REGION} 20 | instance: ${CLOUD_SQL_MYSQL_INSTANCE} 21 | database: ${CLOUD_SQL_MYSQL_DATABASE} 22 | user: ${CLOUD_SQL_MYSQL_USER} 23 | password: ${CLOUD_SQL_MYSQL_PASSWORD} 24 | ipType: ${CLOUD_SQL_MYSQL_IP_TYPE:PUBLIC} 25 | tools: 26 | execute_sql: 27 | kind: mysql-execute-sql 28 | source: cloud-sql-mysql-source 29 | description: Use this tool to execute SQL. 30 | list_active_queries: 31 | kind: mysql-list-active-queries 32 | source: cloud-sql-mysql-source 33 | description: Lists top N (default 10) ongoing queries from processlist and innodb_trx, ordered by execution time in descending order. Returns detailed information of those queries in json format, including process id, query, transaction duration, transaction wait duration, process time, transaction state, process state, username with host, transaction rows locked, transaction rows modified, and db schema. 34 | get_query_plan: 35 | kind: mysql-sql 36 | source: cloud-sql-mysql-source 37 | description: "Provide information about how MySQL executes a SQL statement. Common use cases include: 1) analyze query plan to improve its performance, and 2) determine effectiveness of existing indexes and evalueate new ones." 38 | statement: | 39 | EXPLAIN FORMAT=JSON {{.sql_statement}}; 40 | templateParameters: 41 | - name: sql_statement 42 | type: string 43 | description: "the SQL statement to explain" 44 | required: true 45 | list_tables: 46 | kind: mysql-list-tables 47 | source: cloud-sql-mysql-source 48 | description: "Lists detailed schema information (object type, columns, constraints, indexes, triggers, comment) as JSON for user-created tables (ordinary or partitioned). Filters by a comma-separated list of names. If names are omitted, lists all tables in user schemas." 49 | list_tables_missing_unique_indexes: 50 | kind: mysql-list-tables-missing-unique-indexes 51 | source: cloud-sql-mysql-source 52 | description: "Find tables that do not have primary or unique key constraint. A primary key or unique key is the only mechanism that guaranttes a row is unique. Without them, the database-level protection against data integrity issues will be missing." 53 | list_table_fragmentation: 54 | kind: mysql-list-table-fragmentation 55 | source: cloud-sql-mysql-source 56 | description: List table fragmentation in MySQL, by calculating the size of the data and index files and free space allocated to each table. The query calculates fragmentation percentage which represents the proportion of free space relative to the total data and index size. Storage can be reclaimed for tables with high fragmentation using OPTIMIZE TABLE. 57 | 58 | toolsets: 59 | cloud_sql_mysql_database_tools: 60 | - execute_sql 61 | - list_tables 62 | - get_query_plan 63 | - list_active_queries 64 | - list_tables_missing_unique_indexes 65 | - list_table_fragmentation 66 | ``` -------------------------------------------------------------------------------- /docs/en/getting-started/quickstart/go/genkit/quickstart.go: -------------------------------------------------------------------------------- ```go 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/googleapis/mcp-toolbox-sdk-go/core" 9 | "github.com/googleapis/mcp-toolbox-sdk-go/tbgenkit" 10 | 11 | "github.com/firebase/genkit/go/ai" 12 | "github.com/firebase/genkit/go/genkit" 13 | "github.com/firebase/genkit/go/plugins/googlegenai" 14 | ) 15 | 16 | const systemPrompt = ` 17 | You're a helpful hotel assistant. You handle hotel searching, booking, and 18 | cancellations. When the user searches for a hotel, mention its name, id, 19 | location and price tier. Always mention hotel ids while performing any 20 | searches. This is very important for any operations. For any bookings or 21 | cancellations, please provide the appropriate confirmation. Be sure to 22 | update checkin or checkout dates if mentioned by the user. 23 | Don't ask for confirmations from the user. 24 | ` 25 | 26 | var queries = []string{ 27 | "Find hotels in Basel with Basel in its name.", 28 | "Can you book the hotel Hilton Basel for me?", 29 | "Oh wait, this is too expensive. Please cancel it and book the Hyatt Regency instead.", 30 | "My check in dates would be from April 10, 2024 to April 19, 2024.", 31 | } 32 | 33 | func main() { 34 | ctx := context.Background() 35 | 36 | // Create Toolbox Client 37 | toolboxClient, err := core.NewToolboxClient("http://127.0.0.1:5000") 38 | if err != nil { 39 | log.Fatalf("Failed to create Toolbox client: %v", err) 40 | } 41 | 42 | // Load the tools using the MCP Toolbox SDK. 43 | tools, err := toolboxClient.LoadToolset("my-toolset", ctx) 44 | if err != nil { 45 | log.Fatalf("Failed to load tools: %v\nMake sure your Toolbox server is running and the tool is configured.", err) 46 | } 47 | 48 | // Initialize Genkit 49 | g := genkit.Init(ctx, 50 | genkit.WithPlugins(&googlegenai.GoogleAI{}), 51 | genkit.WithDefaultModel("googleai/gemini-2.0-flash"), 52 | ) 53 | if err != nil { 54 | log.Fatalf("Failed to init genkit: %v\n", err) 55 | } 56 | 57 | // Create a conversation history 58 | conversationHistory := []*ai.Message{ 59 | ai.NewSystemTextMessage(systemPrompt), 60 | } 61 | 62 | // Convert your tool to a Genkit tool. 63 | genkitTools := make([]ai.Tool, len(tools)) 64 | for i, tool := range tools { 65 | newTool, err := tbgenkit.ToGenkitTool(tool, g) 66 | if err != nil { 67 | log.Fatalf("Failed to convert tool: %v\n", err) 68 | } 69 | genkitTools[i] = newTool 70 | } 71 | 72 | toolRefs := make([]ai.ToolRef, len(genkitTools)) 73 | 74 | for i, tool := range genkitTools { 75 | toolRefs[i] = tool 76 | } 77 | 78 | for _, query := range queries { 79 | conversationHistory = append(conversationHistory, ai.NewUserTextMessage(query)) 80 | response, err := genkit.Generate(ctx, g, 81 | ai.WithMessages(conversationHistory...), 82 | ai.WithTools(toolRefs...), 83 | ai.WithReturnToolRequests(true), 84 | ) 85 | 86 | if err != nil { 87 | log.Fatalf("%v\n", err) 88 | } 89 | conversationHistory = append(conversationHistory, response.Message) 90 | 91 | parts := []*ai.Part{} 92 | 93 | for _, req := range response.ToolRequests() { 94 | tool := genkit.LookupTool(g, req.Name) 95 | if tool == nil { 96 | log.Fatalf("tool %q not found", req.Name) 97 | } 98 | 99 | output, err := tool.RunRaw(ctx, req.Input) 100 | if err != nil { 101 | log.Fatalf("tool %q execution failed: %v", tool.Name(), err) 102 | } 103 | 104 | parts = append(parts, 105 | ai.NewToolResponsePart(&ai.ToolResponse{ 106 | Name: req.Name, 107 | Ref: req.Ref, 108 | Output: output, 109 | })) 110 | 111 | } 112 | 113 | if len(parts) > 0 { 114 | resp, err := genkit.Generate(ctx, g, 115 | ai.WithMessages(append(response.History(), ai.NewMessage(ai.RoleTool, nil, parts...))...), 116 | ai.WithTools(toolRefs...), 117 | ) 118 | if err != nil { 119 | log.Fatal(err) 120 | } 121 | fmt.Println("\n", resp.Text()) 122 | conversationHistory = append(conversationHistory, resp.Message) 123 | } else { 124 | fmt.Println("\n", response.Text()) 125 | } 126 | 127 | } 128 | 129 | } 130 | ``` -------------------------------------------------------------------------------- /internal/sources/looker/looker_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package looker_test 16 | 17 | import ( 18 | "testing" 19 | 20 | yaml "github.com/goccy/go-yaml" 21 | "github.com/google/go-cmp/cmp" 22 | "github.com/googleapis/genai-toolbox/internal/server" 23 | "github.com/googleapis/genai-toolbox/internal/sources" 24 | "github.com/googleapis/genai-toolbox/internal/sources/looker" 25 | "github.com/googleapis/genai-toolbox/internal/testutils" 26 | ) 27 | 28 | func TestParseFromYamlLooker(t *testing.T) { 29 | tcs := []struct { 30 | desc string 31 | in string 32 | want server.SourceConfigs 33 | }{ 34 | { 35 | desc: "basic example", 36 | in: ` 37 | sources: 38 | my-looker-instance: 39 | kind: looker 40 | base_url: http://example.looker.com/ 41 | client_id: jasdl;k;tjl 42 | client_secret: sdakl;jgflkasdfkfg 43 | `, 44 | want: map[string]sources.SourceConfig{ 45 | "my-looker-instance": looker.Config{ 46 | Name: "my-looker-instance", 47 | Kind: looker.SourceKind, 48 | BaseURL: "http://example.looker.com/", 49 | ClientId: "jasdl;k;tjl", 50 | ClientSecret: "sdakl;jgflkasdfkfg", 51 | Timeout: "600s", 52 | SslVerification: true, 53 | UseClientOAuth: false, 54 | ShowHiddenModels: true, 55 | ShowHiddenExplores: true, 56 | ShowHiddenFields: true, 57 | Location: "us", 58 | }, 59 | }, 60 | }, 61 | } 62 | for _, tc := range tcs { 63 | t.Run(tc.desc, func(t *testing.T) { 64 | got := struct { 65 | Sources server.SourceConfigs `yaml:"sources"` 66 | }{} 67 | // Parse contents 68 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 69 | if err != nil { 70 | t.Fatalf("unable to unmarshal: %s", err) 71 | } 72 | if !cmp.Equal(tc.want, got.Sources) { 73 | t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources) 74 | } 75 | }) 76 | } 77 | } 78 | 79 | func TestFailParseFromYamlLooker(t *testing.T) { 80 | tcs := []struct { 81 | desc string 82 | in string 83 | err string 84 | }{ 85 | { 86 | desc: "extra field", 87 | in: ` 88 | sources: 89 | my-looker-instance: 90 | kind: looker 91 | base_url: http://example.looker.com/ 92 | client_id: jasdl;k;tjl 93 | client_secret: sdakl;jgflkasdfkfg 94 | schema: test-schema 95 | `, 96 | err: "unable to parse source \"my-looker-instance\" as \"looker\": [5:1] unknown field \"schema\"\n 2 | client_id: jasdl;k;tjl\n 3 | client_secret: sdakl;jgflkasdfkfg\n 4 | kind: looker\n> 5 | schema: test-schema\n ^\n", 97 | }, 98 | { 99 | desc: "missing required field", 100 | in: ` 101 | sources: 102 | my-looker-instance: 103 | kind: looker 104 | client_id: jasdl;k;tjl 105 | `, 106 | err: "unable to parse source \"my-looker-instance\" as \"looker\": Key: 'Config.BaseURL' Error:Field validation for 'BaseURL' failed on the 'required' tag", 107 | }, 108 | } 109 | for _, tc := range tcs { 110 | t.Run(tc.desc, func(t *testing.T) { 111 | got := struct { 112 | Sources server.SourceConfigs `yaml:"sources"` 113 | }{} 114 | // Parse contents 115 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 116 | if err == nil { 117 | t.Fatalf("expect parsing to fail") 118 | } 119 | errStr := err.Error() 120 | if errStr != tc.err { 121 | t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) 122 | } 123 | }) 124 | } 125 | } 126 | ``` -------------------------------------------------------------------------------- /internal/sources/util.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sources 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "fmt" 21 | "io" 22 | "net/http" 23 | "strings" 24 | 25 | "cloud.google.com/go/cloudsqlconn" 26 | "golang.org/x/oauth2/google" 27 | ) 28 | 29 | // GetCloudSQLDialOpts retrieve dial options with the right ip type and user agent for cloud sql 30 | // databases. 31 | func GetCloudSQLOpts(ipType, userAgent string, useIAM bool) ([]cloudsqlconn.Option, error) { 32 | opts := []cloudsqlconn.Option{cloudsqlconn.WithUserAgent(userAgent)} 33 | switch strings.ToLower(ipType) { 34 | case "private": 35 | opts = append(opts, cloudsqlconn.WithDefaultDialOptions(cloudsqlconn.WithPrivateIP())) 36 | case "public": 37 | opts = append(opts, cloudsqlconn.WithDefaultDialOptions(cloudsqlconn.WithPublicIP())) 38 | case "psc": 39 | opts = append(opts, cloudsqlconn.WithDefaultDialOptions(cloudsqlconn.WithPSC())) 40 | default: 41 | return nil, fmt.Errorf("invalid ipType %s. Must be one of `public`, `private`, or `psc`", ipType) 42 | } 43 | 44 | if useIAM { 45 | opts = append(opts, cloudsqlconn.WithIAMAuthN()) 46 | } 47 | return opts, nil 48 | } 49 | 50 | // GetIAMPrincipalEmailFromADC finds the email associated with ADC 51 | func GetIAMPrincipalEmailFromADC(ctx context.Context) (string, error) { 52 | // Finds ADC and returns an HTTP client associated with it 53 | client, err := google.DefaultClient(ctx, 54 | "https://www.googleapis.com/auth/userinfo.email") 55 | if err != nil { 56 | return "", fmt.Errorf("failed to call userinfo endpoint: %w", err) 57 | } 58 | 59 | // Retrieve the email associated with the token 60 | resp, err := client.Get("https://oauth2.googleapis.com/tokeninfo") 61 | if err != nil { 62 | return "", fmt.Errorf("failed to call tokeninfo endpoint: %w", err) 63 | } 64 | defer resp.Body.Close() 65 | 66 | bodyBytes, err := io.ReadAll(resp.Body) 67 | if err != nil { 68 | return "", fmt.Errorf("error reading response body %d: %s", resp.StatusCode, string(bodyBytes)) 69 | } 70 | if resp.StatusCode != http.StatusOK { 71 | return "", fmt.Errorf("tokeninfo endpoint returned non-OK status %d: %s", resp.StatusCode, string(bodyBytes)) 72 | } 73 | 74 | // Unmarshal response body and get `email` 75 | var responseJSON map[string]any 76 | err = json.Unmarshal(bodyBytes, &responseJSON) 77 | if err != nil { 78 | 79 | return "", fmt.Errorf("error parsing JSON: %v", err) 80 | } 81 | 82 | emailValue, ok := responseJSON["email"] 83 | if !ok { 84 | return "", fmt.Errorf("email not found in response: %v", err) 85 | } 86 | // service account email used for IAM should trim the suffix 87 | email := strings.TrimSuffix(emailValue.(string), ".gserviceaccount.com") 88 | return email, nil 89 | } 90 | 91 | func GetIAMAccessToken(ctx context.Context) (string, error) { 92 | creds, err := google.FindDefaultCredentials(ctx, "https://www.googleapis.com/auth/cloud-platform") 93 | if err != nil { 94 | return "", fmt.Errorf("failed to find default credentials (run 'gcloud auth application-default login'?): %w", err) 95 | } 96 | 97 | token, err := creds.TokenSource.Token() // This gets an oauth2.Token 98 | if err != nil { 99 | return "", fmt.Errorf("failed to get token from token source: %w", err) 100 | } 101 | 102 | if !token.Valid() { 103 | return "", fmt.Errorf("retrieved token is invalid or expired") 104 | } 105 | return token.AccessToken, nil 106 | } 107 | ``` -------------------------------------------------------------------------------- /internal/sources/tidb/tidb.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tidb 16 | 17 | import ( 18 | "context" 19 | "database/sql" 20 | "fmt" 21 | "regexp" 22 | 23 | _ "github.com/go-sql-driver/mysql" 24 | "github.com/goccy/go-yaml" 25 | "github.com/googleapis/genai-toolbox/internal/sources" 26 | "go.opentelemetry.io/otel/trace" 27 | ) 28 | 29 | const SourceKind string = "tidb" 30 | const TiDBCloudHostPattern string = `gateway\d{2}\.(.+)\.(prod|dev|staging)\.(.+)\.tidbcloud\.com` 31 | 32 | // validate interface 33 | var _ sources.SourceConfig = Config{} 34 | 35 | func init() { 36 | if !sources.Register(SourceKind, newConfig) { 37 | panic(fmt.Sprintf("source kind %q already registered", SourceKind)) 38 | } 39 | } 40 | 41 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) { 42 | actual := Config{Name: name} 43 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 44 | return nil, err 45 | } 46 | 47 | // If the host is a TiDB Cloud instance, force to use SSL 48 | if IsTiDBCloudHost(actual.Host) { 49 | actual.UseSSL = true 50 | } 51 | 52 | return actual, nil 53 | } 54 | 55 | type Config struct { 56 | Name string `yaml:"name" validate:"required"` 57 | Kind string `yaml:"kind" validate:"required"` 58 | Host string `yaml:"host" validate:"required"` 59 | Port string `yaml:"port" validate:"required"` 60 | User string `yaml:"user" validate:"required"` 61 | Password string `yaml:"password" validate:"required"` 62 | Database string `yaml:"database" validate:"required"` 63 | UseSSL bool `yaml:"ssl"` 64 | } 65 | 66 | func (r Config) SourceConfigKind() string { 67 | return SourceKind 68 | } 69 | 70 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { 71 | pool, err := initTiDBConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Database, r.UseSSL) 72 | if err != nil { 73 | return nil, fmt.Errorf("unable to create pool: %w", err) 74 | } 75 | 76 | err = pool.PingContext(ctx) 77 | if err != nil { 78 | return nil, fmt.Errorf("unable to connect successfully: %w", err) 79 | } 80 | 81 | s := &Source{ 82 | Name: r.Name, 83 | Kind: SourceKind, 84 | Pool: pool, 85 | } 86 | return s, nil 87 | } 88 | 89 | var _ sources.Source = &Source{} 90 | 91 | type Source struct { 92 | Name string `yaml:"name"` 93 | Kind string `yaml:"kind"` 94 | Pool *sql.DB 95 | } 96 | 97 | func (s *Source) SourceKind() string { 98 | return SourceKind 99 | } 100 | 101 | func (s *Source) TiDBPool() *sql.DB { 102 | return s.Pool 103 | } 104 | 105 | func IsTiDBCloudHost(host string) bool { 106 | pattern := `gateway\d{2}\.(.+)\.(prod|dev|staging)\.(.+)\.tidbcloud\.com` 107 | match, err := regexp.MatchString(pattern, host) 108 | if err != nil { 109 | return false 110 | } 111 | return match 112 | } 113 | 114 | func initTiDBConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, pass, dbname string, useSSL bool) (*sql.DB, error) { 115 | //nolint:all // Reassigned ctx 116 | ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name) 117 | defer span.End() 118 | 119 | // Configure the driver to connect to the database 120 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&charset=utf8mb4&tls=%t", user, pass, host, port, dbname, useSSL) 121 | 122 | // Interact with the driver directly as you normally would 123 | pool, err := sql.Open("mysql", dsn) 124 | if err != nil { 125 | return nil, fmt.Errorf("sql.Open: %w", err) 126 | } 127 | return pool, nil 128 | } 129 | ``` -------------------------------------------------------------------------------- /internal/tools/dataform/dataformcompilelocal/dataformcompilelocal.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dataformcompilelocal 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "os/exec" 21 | "strings" 22 | 23 | "github.com/goccy/go-yaml" 24 | "github.com/googleapis/genai-toolbox/internal/sources" 25 | "github.com/googleapis/genai-toolbox/internal/tools" 26 | ) 27 | 28 | const kind string = "dataform-compile-local" 29 | 30 | func init() { 31 | if !tools.Register(kind, newConfig) { 32 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 33 | } 34 | } 35 | 36 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 37 | actual := Config{Name: name} 38 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 39 | return nil, err 40 | } 41 | return actual, nil 42 | } 43 | 44 | type Config struct { 45 | Name string `yaml:"name" validate:"required"` 46 | Kind string `yaml:"kind" validate:"required"` 47 | Description string `yaml:"description" validate:"required"` 48 | AuthRequired []string `yaml:"authRequired"` 49 | } 50 | 51 | var _ tools.ToolConfig = Config{} 52 | 53 | func (cfg Config) ToolConfigKind() string { 54 | return kind 55 | } 56 | 57 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 58 | allParameters := tools.Parameters{ 59 | tools.NewStringParameter("project_dir", "The Dataform project directory."), 60 | } 61 | paramManifest := allParameters.Manifest() 62 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) 63 | 64 | t := Tool{ 65 | Name: cfg.Name, 66 | Kind: kind, 67 | AuthRequired: cfg.AuthRequired, 68 | Parameters: allParameters, 69 | manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 70 | mcpManifest: mcpManifest, 71 | } 72 | 73 | return t, nil 74 | } 75 | 76 | var _ tools.Tool = Tool{} 77 | 78 | type Tool struct { 79 | Name string `yaml:"name"` 80 | Kind string `yaml:"kind"` 81 | AuthRequired []string `yaml:"authRequired"` 82 | Parameters tools.Parameters `yaml:"allParams"` 83 | manifest tools.Manifest 84 | mcpManifest tools.McpManifest 85 | } 86 | 87 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 88 | paramsMap := params.AsMap() 89 | 90 | projectDir, ok := paramsMap["project_dir"].(string) 91 | if !ok || projectDir == "" { 92 | return nil, fmt.Errorf("error casting 'project_dir' to string or invalid value") 93 | } 94 | 95 | cmd := exec.CommandContext(ctx, "dataform", "compile", projectDir, "--json") 96 | output, err := cmd.CombinedOutput() 97 | if err != nil { 98 | return nil, fmt.Errorf("error executing dataform compile: %w\nOutput: %s", err, string(output)) 99 | } 100 | 101 | return strings.TrimSpace(string(output)), nil 102 | } 103 | 104 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 105 | return tools.ParseParams(t.Parameters, data, claims) 106 | } 107 | 108 | func (t Tool) Manifest() tools.Manifest { 109 | return t.manifest 110 | } 111 | 112 | func (t Tool) McpManifest() tools.McpManifest { 113 | return t.mcpManifest 114 | } 115 | 116 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 117 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 118 | } 119 | 120 | func (t Tool) RequiresClientAuthorization() bool { 121 | return false 122 | } 123 | ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/yugabytedb.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "YugabyteDB" 3 | type: docs 4 | weight: 1 5 | description: > 6 | YugabyteDB is a high-performance, distributed SQL database. 7 | --- 8 | 9 | ## About 10 | 11 | [YugabyteDB][yugabytedb] is a high-performance, distributed SQL database 12 | designed for global, internet-scale applications, with full PostgreSQL 13 | compatibility. 14 | 15 | [yugabytedb]: https://www.yugabyte.com/ 16 | 17 | ## Example 18 | 19 | ```yaml 20 | sources: 21 | my-yb-source: 22 | kind: yugabytedb 23 | host: 127.0.0.1 24 | port: 5433 25 | database: yugabyte 26 | user: ${USER_NAME} 27 | password: ${PASSWORD} 28 | loadBalance: true 29 | topologyKeys: cloud.region.zone1:1,cloud.region.zone2:2 30 | ``` 31 | 32 | ## Reference 33 | 34 | | **field** | **type** | **required** | **description** | 35 | |------------------------------|:--------:|:------------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| 36 | | kind | string | true | Must be "yugabytedb". | 37 | | host | string | true | IP address to connect to. | 38 | | port | integer | true | Port to connect to. The default port is 5433. | 39 | | database | string | true | Name of the YugabyteDB database to connect to. The default database name is yugabyte. | 40 | | user | string | true | Name of the YugabyteDB user to connect as. The default user is yugabyte. | 41 | | password | string | true | Password of the YugabyteDB user. The default password is yugabyte. | 42 | | loadBalance | boolean | false | If true, enable uniform load balancing. The default loadBalance value is false. | 43 | | topologyKeys | string | false | Comma-separated geo-locations in the form cloud.region.zone:priority to enable topology-aware load balancing. Ignored if loadBalance is false. It is null by default. | 44 | | ybServersRefreshInterval | integer | false | The interval (in seconds) to refresh the servers list; ignored if loadBalance is false. The default value of ybServersRefreshInterval is 300. | 45 | | fallbackToTopologyKeysOnly | boolean | false | If set to true and topologyKeys are specified, only connect to nodes specified in topologyKeys. By defualt, this is set to false. | 46 | | failedHostReconnectDelaySecs | integer | false | Time (in seconds) to wait before trying to connect to failed nodes. The default value of is 5. | 47 | ``` -------------------------------------------------------------------------------- /internal/sources/spanner/spanner.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package spanner 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | "cloud.google.com/go/spanner" 22 | "github.com/goccy/go-yaml" 23 | "github.com/googleapis/genai-toolbox/internal/sources" 24 | "github.com/googleapis/genai-toolbox/internal/util" 25 | "go.opentelemetry.io/otel/trace" 26 | ) 27 | 28 | const SourceKind string = "spanner" 29 | 30 | // validate interface 31 | var _ sources.SourceConfig = Config{} 32 | 33 | func init() { 34 | if !sources.Register(SourceKind, newConfig) { 35 | panic(fmt.Sprintf("source kind %q already registered", SourceKind)) 36 | } 37 | } 38 | 39 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) { 40 | actual := Config{Name: name, Dialect: "googlesql"} // Default dialect 41 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 42 | return nil, err 43 | } 44 | return actual, nil 45 | } 46 | 47 | type Config struct { 48 | Name string `yaml:"name" validate:"required"` 49 | Kind string `yaml:"kind" validate:"required"` 50 | Project string `yaml:"project" validate:"required"` 51 | Instance string `yaml:"instance" validate:"required"` 52 | Dialect sources.Dialect `yaml:"dialect" validate:"required"` 53 | Database string `yaml:"database" validate:"required"` 54 | } 55 | 56 | func (r Config) SourceConfigKind() string { 57 | return SourceKind 58 | } 59 | 60 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { 61 | client, err := initSpannerClient(ctx, tracer, r.Name, r.Project, r.Instance, r.Database) 62 | if err != nil { 63 | return nil, fmt.Errorf("unable to create client: %w", err) 64 | } 65 | 66 | s := &Source{ 67 | Name: r.Name, 68 | Kind: SourceKind, 69 | Client: client, 70 | Dialect: r.Dialect.String(), 71 | } 72 | return s, nil 73 | } 74 | 75 | var _ sources.Source = &Source{} 76 | 77 | type Source struct { 78 | Name string `yaml:"name"` 79 | Kind string `yaml:"kind"` 80 | Client *spanner.Client 81 | Dialect string 82 | } 83 | 84 | func (s *Source) SourceKind() string { 85 | return SourceKind 86 | } 87 | 88 | func (s *Source) SpannerClient() *spanner.Client { 89 | return s.Client 90 | } 91 | 92 | func (s *Source) DatabaseDialect() string { 93 | return s.Dialect 94 | } 95 | 96 | func initSpannerClient(ctx context.Context, tracer trace.Tracer, name, project, instance, dbname string) (*spanner.Client, error) { 97 | //nolint:all // Reassigned ctx 98 | ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name) 99 | defer span.End() 100 | 101 | // Configure the connection to the database 102 | db := fmt.Sprintf("projects/%s/instances/%s/databases/%s", project, instance, dbname) 103 | 104 | // Configure session pool to automatically clean inactive transactions 105 | sessionPoolConfig := spanner.SessionPoolConfig{ 106 | TrackSessionHandles: true, 107 | InactiveTransactionRemovalOptions: spanner.InactiveTransactionRemovalOptions{ 108 | ActionOnInactiveTransaction: spanner.WarnAndClose, 109 | }, 110 | } 111 | 112 | // Create spanner client 113 | userAgent, err := util.UserAgentFromContext(ctx) 114 | if err != nil { 115 | return nil, err 116 | } 117 | client, err := spanner.NewClientWithConfig(ctx, db, spanner.ClientConfig{SessionPoolConfig: sessionPoolConfig, UserAgent: userAgent}) 118 | if err != nil { 119 | return nil, fmt.Errorf("unable to create new client: %w", err) 120 | } 121 | 122 | return client, nil 123 | } 124 | ``` -------------------------------------------------------------------------------- /internal/prebuiltconfigs/tools/mysql.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | sources: 16 | mysql-source: 17 | kind: mysql 18 | host: ${MYSQL_HOST} 19 | port: ${MYSQL_PORT} 20 | database: ${MYSQL_DATABASE} 21 | user: ${MYSQL_USER} 22 | password: ${MYSQL_PASSWORD} 23 | # Optional: supply additional DSN parameters (e.g. TLS, charset) via env var. 24 | # Provide a YAML-encoded map in MYSQL_QUERY_PARAMS, for example: 25 | # export MYSQL_QUERY_PARAMS="{tls: preferred, charset: utf8mb4}" 26 | # When the variable is empty/undefined, queryParams will be treated as nil. 27 | queryParams: ${MYSQL_QUERY_PARAMS:} 28 | queryTimeout: 30s # Optional 29 | tools: 30 | execute_sql: 31 | kind: mysql-execute-sql 32 | source: mysql-source 33 | description: Use this tool to execute SQL. 34 | list_active_queries: 35 | kind: mysql-list-active-queries 36 | source: mysql-source 37 | description: Lists top N (default 10) ongoing queries from processlist and innodb_trx, ordered by execution time in descending order. Returns detailed information of those queries in json format, including process id, query, transaction duration, transaction wait duration, process time, transaction state, process state, username with host, transaction rows locked, transaction rows modified, and db schema. 38 | get_query_plan: 39 | kind: mysql-sql 40 | source: mysql-source 41 | description: "Provide information about how MySQL executes a SQL statement. Common use cases include: 1) analyze query plan to improve its performance, and 2) determine effectiveness of existing indexes and evalueate new ones." 42 | statement: | 43 | EXPLAIN FORMAT=JSON {{.sql_statement}}; 44 | templateParameters: 45 | - name: sql_statement 46 | type: string 47 | description: "the SQL statement to explain" 48 | required: true 49 | list_tables: 50 | kind: mysql-list-tables 51 | source: mysql-source 52 | description: "Lists detailed schema information (object type, columns, constraints, indexes, triggers, comment) as JSON for user-created tables (ordinary or partitioned). Filters by a comma-separated list of names. If names are omitted, lists all tables in user schemas." 53 | list_tables_missing_unique_indexes: 54 | kind: mysql-list-tables-missing-unique-indexes 55 | source: mysql-source 56 | description: "Find tables that do not have primary or unique key constraint. A primary key or unique key is the only mechanism that guaranttes a row is unique. Without them, the database-level protection against data integrity issues will be missing." 57 | list_table_fragmentation: 58 | kind: mysql-list-table-fragmentation 59 | source: mysql-source 60 | description: List table fragmentation in MySQL, by calculating the size of the data and index files and free space allocated to each table. The query calculates fragmentation percentage which represents the proportion of free space relative to the total data and index size. Storage can be reclaimed for tables with high fragmentation using OPTIMIZE TABLE. 61 | 62 | toolsets: 63 | mysql_database_tools: 64 | - execute_sql 65 | - list_tables 66 | - get_query_plan 67 | - list_active_queries 68 | - list_tables_missing_unique_indexes 69 | - list_table_fragmentation 70 | ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/mssql.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "SQL Server" 3 | type: docs 4 | weight: 1 5 | description: > 6 | SQL Server is a relational database management system (RDBMS). 7 | 8 | --- 9 | 10 | ## About 11 | 12 | [SQL Server][mssql-docs] is a relational database management system (RDBMS) 13 | developed by Microsoft that allows users to store, retrieve, and manage large 14 | amount of data through a structured format. 15 | 16 | [mssql-docs]: https://www.microsoft.com/en-us/sql-server 17 | 18 | ## Available Tools 19 | 20 | - [`mssql-sql`](../tools/mssql/mssql-sql.md) 21 | Execute pre-defined SQL Server queries with placeholder parameters. 22 | 23 | - [`mssql-execute-sql`](../tools/mssql/mssql-execute-sql.md) 24 | Run parameterized SQL Server queries in SQL Server. 25 | 26 | - [`mssql-list-tables`](../tools/mssql/mssql-list-tables.md) 27 | List tables in a SQL Server database. 28 | 29 | ## Requirements 30 | 31 | ### Database User 32 | 33 | This source only uses standard authentication. You will need to [create a 34 | SQL Server user][mssql-users] to login to the database with. 35 | 36 | [mssql-users]: https://learn.microsoft.com/en-us/sql/relational-databases/security/authentication-access/create-a-database-user?view=sql-server-ver16 37 | 38 | ## Example 39 | 40 | ```yaml 41 | sources: 42 | my-mssql-source: 43 | kind: mssql 44 | host: 127.0.0.1 45 | port: 1433 46 | database: my_db 47 | user: ${USER_NAME} 48 | password: ${PASSWORD} 49 | # encrypt: strict 50 | ``` 51 | 52 | {{< notice tip >}} 53 | Use environment variable replacement with the format ${ENV_NAME} 54 | instead of hardcoding your secrets into the configuration file. 55 | {{< /notice >}} 56 | 57 | ## Reference 58 | 59 | | **field** | **type** | **required** | **description** | 60 | |-----------|:--------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 61 | | kind | string | true | Must be "mssql". | 62 | | host | string | true | IP address to connect to (e.g. "127.0.0.1"). | 63 | | port | string | true | Port to connect to (e.g. "1433"). | 64 | | database | string | true | Name of the SQL Server database to connect to (e.g. "my_db"). | 65 | | user | string | true | Name of the SQL Server user to connect as (e.g. "my-user"). | 66 | | password | string | true | Password of the SQL Server user (e.g. "my-password"). | 67 | | encrypt | string | false | Encryption level for data transmitted between the client and server (e.g., "strict"). If not specified, defaults to the [github.com/microsoft/go-mssqldb](https://github.com/microsoft/go-mssqldb?tab=readme-ov-file#common-parameters) package's default encrypt value. | 68 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/bigquery/bigquery-analyze-contribution.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "bigquery-analyze-contribution" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "bigquery-analyze-contribution" tool performs contribution analysis in BigQuery. 7 | aliases: 8 | - /resources/tools/bigquery-analyze-contribution 9 | --- 10 | 11 | ## About 12 | 13 | A `bigquery-analyze-contribution` tool performs contribution analysis in 14 | BigQuery by creating a temporary `CONTRIBUTION_ANALYSIS` model and then querying 15 | it with `ML.GET_INSIGHTS` to find top contributors for a given metric. 16 | 17 | It's compatible with the following sources: 18 | 19 | - [bigquery](../../sources/bigquery.md) 20 | 21 | `bigquery-analyze-contribution` takes the following parameters: 22 | 23 | - **input_data** (string, required): The data that contain the test and control 24 | data to analyze. This can be a fully qualified BigQuery table ID (e.g., 25 | `my-project.my_dataset.my_table`) or a SQL query that returns the data. 26 | - **contribution_metric** (string, required): The name of the column that 27 | contains the metric to analyze. This can be SUM(metric_column_name), 28 | SUM(numerator_metric_column_name)/SUM(denominator_metric_column_name) or 29 | SUM(metric_sum_column_name)/COUNT(DISTINCT categorical_column_name) depending 30 | the type of metric to analyze. 31 | - **is_test_col** (string, required): The name of the column that identifies 32 | whether a row is in the test or control group. The column must contain boolean 33 | values. 34 | - **dimension_id_cols** (array of strings, optional): An array of column names 35 | that uniquely identify each dimension. 36 | - **top_k_insights_by_apriori_support** (integer, optional): The number of top 37 | insights to return, ranked by apriori support. Default to '30'. 38 | - **pruning_method** (string, optional): The method to use for pruning redundant 39 | insights. Can be `'NO_PRUNING'` or `'PRUNE_REDUNDANT_INSIGHTS'`. Defaults to 40 | `'PRUNE_REDUNDANT_INSIGHTS'`. 41 | 42 | The behavior of this tool is influenced by the `writeMode` setting on its `bigquery` source: 43 | 44 | - **`allowed` (default) and `blocked`:** These modes do not impose any special restrictions on the `bigquery-analyze-contribution` tool. 45 | - **`protected`:** This mode enables session-based execution. The tool will operate within the same BigQuery session as other 46 | tools using the same source. This allows the `input_data` parameter to be a query that references temporary resources (e.g., 47 | `TEMP` tables) created within that session. 48 | 49 | 50 | ## Example 51 | 52 | ```yaml 53 | tools: 54 | contribution_analyzer: 55 | kind: bigquery-analyze-contribution 56 | source: my-bigquery-source 57 | description: Use this tool to run contribution analysis on a dataset in BigQuery. 58 | ``` 59 | 60 | ## Sample Prompt 61 | You can prepare a sample table following 62 | https://cloud.google.com/bigquery/docs/get-contribution-analysis-insights. 63 | And use the following sample prompts to call this tool: 64 | 65 | - What drives the changes in sales in the table 66 | `bqml_tutorial.iowa_liquor_sales_sum_data`? Use the project id myproject. 67 | - Analyze the contribution for the `total_sales` metric in the table 68 | `bqml_tutorial.iowa_liquor_sales_sum_data`. The test group is identified by 69 | the `is_test` column. The dimensions are `store_name`, `city`, `vendor_name`, 70 | `category_name` and `item_description`. 71 | 72 | ## Reference 73 | 74 | | **field** | **type** | **required** | **description** | 75 | |-------------|:--------:|:------------:|------------------------------------------------------------| 76 | | kind | string | true | Must be "bigquery-analyze-contribution". | 77 | | source | string | true | Name of the source the tool should execute on. | 78 | | description | string | true | Description of the tool that is passed to the LLM. | 79 | ``` -------------------------------------------------------------------------------- /internal/tools/mongodb/mongodbdeleteone/mongodbdeleteone_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mongodbdeleteone_test 16 | 17 | import ( 18 | "strings" 19 | "testing" 20 | 21 | "github.com/googleapis/genai-toolbox/internal/tools" 22 | "github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbdeleteone" 23 | 24 | yaml "github.com/goccy/go-yaml" 25 | "github.com/google/go-cmp/cmp" 26 | "github.com/googleapis/genai-toolbox/internal/server" 27 | "github.com/googleapis/genai-toolbox/internal/testutils" 28 | ) 29 | 30 | func TestParseFromYamlMongoQuery(t *testing.T) { 31 | ctx, err := testutils.ContextWithNewLogger() 32 | if err != nil { 33 | t.Fatalf("unexpected error: %s", err) 34 | } 35 | tcs := []struct { 36 | desc string 37 | in string 38 | want server.ToolConfigs 39 | }{ 40 | { 41 | desc: "basic example", 42 | in: ` 43 | tools: 44 | example_tool: 45 | kind: mongodb-delete-one 46 | source: my-instance 47 | description: some description 48 | database: test_db 49 | collection: test_coll 50 | filterPayload: | 51 | { name: {{json .name}} } 52 | filterParams: 53 | - name: name 54 | type: string 55 | description: small description 56 | `, 57 | want: server.ToolConfigs{ 58 | "example_tool": mongodbdeleteone.Config{ 59 | Name: "example_tool", 60 | Kind: "mongodb-delete-one", 61 | Source: "my-instance", 62 | AuthRequired: []string{}, 63 | Database: "test_db", 64 | Collection: "test_coll", 65 | Description: "some description", 66 | FilterPayload: "{ name: {{json .name}} }\n", 67 | FilterParams: tools.Parameters{ 68 | &tools.StringParameter{ 69 | CommonParameter: tools.CommonParameter{ 70 | Name: "name", 71 | Type: "string", 72 | Desc: "small description", 73 | }, 74 | }, 75 | }, 76 | }, 77 | }, 78 | }, 79 | } 80 | for _, tc := range tcs { 81 | t.Run(tc.desc, func(t *testing.T) { 82 | got := struct { 83 | Tools server.ToolConfigs `yaml:"tools"` 84 | }{} 85 | // Parse contents 86 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 87 | if err != nil { 88 | t.Fatalf("unable to unmarshal: %s", err) 89 | } 90 | if diff := cmp.Diff(tc.want, got.Tools); diff != "" { 91 | t.Fatalf("incorrect parse: diff %v", diff) 92 | } 93 | }) 94 | } 95 | 96 | } 97 | 98 | func TestFailParseFromYamlMongoQuery(t *testing.T) { 99 | ctx, err := testutils.ContextWithNewLogger() 100 | if err != nil { 101 | t.Fatalf("unexpected error: %s", err) 102 | } 103 | tcs := []struct { 104 | desc string 105 | in string 106 | err string 107 | }{ 108 | { 109 | desc: "Invalid method", 110 | in: ` 111 | tools: 112 | example_tool: 113 | kind: mongodb-delete-one 114 | source: my-instance 115 | description: some description 116 | collection: test_coll 117 | filterPayload: | 118 | { name : {{json .name}} } 119 | `, 120 | err: `unable to parse tool "example_tool" as kind "mongodb-delete-one"`, 121 | }, 122 | } 123 | for _, tc := range tcs { 124 | t.Run(tc.desc, func(t *testing.T) { 125 | got := struct { 126 | Tools server.ToolConfigs `yaml:"tools"` 127 | }{} 128 | // Parse contents 129 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 130 | if err == nil { 131 | t.Fatalf("expect parsing to fail") 132 | } 133 | errStr := err.Error() 134 | if !strings.Contains(errStr, tc.err) { 135 | t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err) 136 | } 137 | }) 138 | } 139 | 140 | } 141 | ``` -------------------------------------------------------------------------------- /internal/tools/mongodb/mongodbdeletemany/mongodbdeletemany_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mongodbdeletemany_test 16 | 17 | import ( 18 | "strings" 19 | "testing" 20 | 21 | "github.com/googleapis/genai-toolbox/internal/tools" 22 | "github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbdeletemany" 23 | 24 | yaml "github.com/goccy/go-yaml" 25 | "github.com/google/go-cmp/cmp" 26 | "github.com/googleapis/genai-toolbox/internal/server" 27 | "github.com/googleapis/genai-toolbox/internal/testutils" 28 | ) 29 | 30 | func TestParseFromYamlMongoQuery(t *testing.T) { 31 | ctx, err := testutils.ContextWithNewLogger() 32 | if err != nil { 33 | t.Fatalf("unexpected error: %s", err) 34 | } 35 | tcs := []struct { 36 | desc string 37 | in string 38 | want server.ToolConfigs 39 | }{ 40 | { 41 | desc: "basic example", 42 | in: ` 43 | tools: 44 | example_tool: 45 | kind: mongodb-delete-many 46 | source: my-instance 47 | description: some description 48 | database: test_db 49 | collection: test_coll 50 | filterPayload: | 51 | { name: {{json .name}} } 52 | filterParams: 53 | - name: name 54 | type: string 55 | description: small description 56 | `, 57 | want: server.ToolConfigs{ 58 | "example_tool": mongodbdeletemany.Config{ 59 | Name: "example_tool", 60 | Kind: "mongodb-delete-many", 61 | Source: "my-instance", 62 | AuthRequired: []string{}, 63 | Database: "test_db", 64 | Collection: "test_coll", 65 | Description: "some description", 66 | FilterPayload: "{ name: {{json .name}} }\n", 67 | FilterParams: tools.Parameters{ 68 | &tools.StringParameter{ 69 | CommonParameter: tools.CommonParameter{ 70 | Name: "name", 71 | Type: "string", 72 | Desc: "small description", 73 | }, 74 | }, 75 | }, 76 | }, 77 | }, 78 | }, 79 | } 80 | for _, tc := range tcs { 81 | t.Run(tc.desc, func(t *testing.T) { 82 | got := struct { 83 | Tools server.ToolConfigs `yaml:"tools"` 84 | }{} 85 | // Parse contents 86 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 87 | if err != nil { 88 | t.Fatalf("unable to unmarshal: %s", err) 89 | } 90 | if diff := cmp.Diff(tc.want, got.Tools); diff != "" { 91 | t.Fatalf("incorrect parse: diff %v", diff) 92 | } 93 | }) 94 | } 95 | 96 | } 97 | 98 | func TestFailParseFromYamlMongoQuery(t *testing.T) { 99 | ctx, err := testutils.ContextWithNewLogger() 100 | if err != nil { 101 | t.Fatalf("unexpected error: %s", err) 102 | } 103 | tcs := []struct { 104 | desc string 105 | in string 106 | err string 107 | }{ 108 | { 109 | desc: "Invalid method", 110 | in: ` 111 | tools: 112 | example_tool: 113 | kind: mongodb-delete-many 114 | source: my-instance 115 | description: some description 116 | collection: test_coll 117 | filterPayload: | 118 | { name : {{json .name}} } 119 | `, 120 | err: `unable to parse tool "example_tool" as kind "mongodb-delete-many"`, 121 | }, 122 | } 123 | for _, tc := range tcs { 124 | t.Run(tc.desc, func(t *testing.T) { 125 | got := struct { 126 | Tools server.ToolConfigs `yaml:"tools"` 127 | }{} 128 | // Parse contents 129 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 130 | if err == nil { 131 | t.Fatalf("expect parsing to fail") 132 | } 133 | errStr := err.Error() 134 | if !strings.Contains(errStr, tc.err) { 135 | t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err) 136 | } 137 | }) 138 | } 139 | 140 | } 141 | ``` -------------------------------------------------------------------------------- /internal/sources/mssql/mssql.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mssql 16 | 17 | import ( 18 | "context" 19 | "database/sql" 20 | "fmt" 21 | "net/url" 22 | 23 | "github.com/goccy/go-yaml" 24 | "github.com/googleapis/genai-toolbox/internal/sources" 25 | "github.com/googleapis/genai-toolbox/internal/util" 26 | _ "github.com/microsoft/go-mssqldb" 27 | "go.opentelemetry.io/otel/trace" 28 | ) 29 | 30 | const SourceKind string = "mssql" 31 | 32 | // validate interface 33 | var _ sources.SourceConfig = Config{} 34 | 35 | func init() { 36 | if !sources.Register(SourceKind, newConfig) { 37 | panic(fmt.Sprintf("source kind %q already registered", SourceKind)) 38 | } 39 | } 40 | 41 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) { 42 | actual := Config{Name: name} 43 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 44 | return nil, err 45 | } 46 | return actual, nil 47 | } 48 | 49 | type Config struct { 50 | // Cloud SQL MSSQL configs 51 | Name string `yaml:"name" validate:"required"` 52 | Kind string `yaml:"kind" validate:"required"` 53 | Host string `yaml:"host" validate:"required"` 54 | Port string `yaml:"port" validate:"required"` 55 | User string `yaml:"user" validate:"required"` 56 | Password string `yaml:"password" validate:"required"` 57 | Database string `yaml:"database" validate:"required"` 58 | Encrypt string `yaml:"encrypt"` 59 | } 60 | 61 | func (r Config) SourceConfigKind() string { 62 | // Returns Cloud SQL MSSQL source kind 63 | return SourceKind 64 | } 65 | 66 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { 67 | // Initializes a MSSQL source 68 | db, err := initMssqlConnection(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Database, r.Encrypt) 69 | if err != nil { 70 | return nil, fmt.Errorf("unable to create db connection: %w", err) 71 | } 72 | 73 | // Verify db connection 74 | err = db.PingContext(ctx) 75 | if err != nil { 76 | return nil, fmt.Errorf("unable to connect successfully: %w", err) 77 | } 78 | 79 | s := &Source{ 80 | Name: r.Name, 81 | Kind: SourceKind, 82 | Db: db, 83 | } 84 | return s, nil 85 | } 86 | 87 | var _ sources.Source = &Source{} 88 | 89 | type Source struct { 90 | // Cloud SQL MSSQL struct with connection pool 91 | Name string `yaml:"name"` 92 | Kind string `yaml:"kind"` 93 | Db *sql.DB 94 | } 95 | 96 | func (s *Source) SourceKind() string { 97 | // Returns Cloud SQL MSSQL source kind 98 | return SourceKind 99 | } 100 | 101 | func (s *Source) MSSQLDB() *sql.DB { 102 | // Returns a Cloud SQL MSSQL database connection pool 103 | return s.Db 104 | } 105 | 106 | func initMssqlConnection( 107 | ctx context.Context, 108 | tracer trace.Tracer, 109 | name, host, port, user, pass, dbname, encrypt string, 110 | ) ( 111 | *sql.DB, 112 | error, 113 | ) { 114 | //nolint:all // Reassigned ctx 115 | ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name) 116 | defer span.End() 117 | 118 | userAgent, err := util.UserAgentFromContext(ctx) 119 | if err != nil { 120 | userAgent = "genai-toolbox" 121 | } 122 | // Create dsn 123 | query := url.Values{} 124 | query.Add("app name", userAgent) 125 | query.Add("database", dbname) 126 | if encrypt != "" { 127 | query.Add("encrypt", encrypt) 128 | } 129 | 130 | url := &url.URL{ 131 | Scheme: "sqlserver", 132 | User: url.UserPassword(user, pass), 133 | Host: fmt.Sprintf("%s:%s", host, port), 134 | RawQuery: query.Encode(), 135 | } 136 | 137 | // Open database connection 138 | db, err := sql.Open("sqlserver", url.String()) 139 | if err != nil { 140 | return nil, fmt.Errorf("sql.Open: %w", err) 141 | } 142 | return db, nil 143 | } 144 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/mongodb/mongodb-aggregate.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "mongodb-aggregate" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "mongodb-aggregate" tool executes a multi-stage aggregation pipeline against a MongoDB collection. 7 | aliases: 8 | - /resources/tools/mongodb-aggregate 9 | --- 10 | 11 | ## About 12 | 13 | The `mongodb-aggregate` tool is the most powerful query tool for MongoDB, 14 | allowing you to process data through a multi-stage pipeline. Each stage 15 | transforms the documents as they pass through, enabling complex operations like 16 | grouping, filtering, reshaping documents, and performing calculations. 17 | 18 | The core of this tool is the `pipelinePayload`, which must be a string 19 | containing a **JSON array of pipeline stage documents**. The tool returns a JSON 20 | array of documents produced by the final stage of the pipeline. 21 | 22 | A `readOnly` flag can be set to `true` as a safety measure to ensure the 23 | pipeline does not contain any write stages (like `$out` or `$merge`). 24 | 25 | This tool is compatible with the following source kind: 26 | 27 | * [`mongodb`](../../sources/mongodb.md) 28 | 29 | ## Example 30 | 31 | Here is an example that calculates the average price and total count of products 32 | for each category, but only for products with an "active" status. 33 | 34 | ```yaml 35 | tools: 36 | get_category_stats: 37 | kind: mongodb-aggregate 38 | source: my-mongo-source 39 | description: Calculates average price and count of products, grouped by category. 40 | database: ecommerce 41 | collection: products 42 | readOnly: true 43 | pipelinePayload: | 44 | [ 45 | { 46 | "$match": { 47 | "status": {{json .status_filter}} 48 | } 49 | }, 50 | { 51 | "$group": { 52 | "_id": "$category", 53 | "average_price": { "$avg": "$price" }, 54 | "item_count": { "$sum": 1 } 55 | } 56 | }, 57 | { 58 | "$sort": { 59 | "average_price": -1 60 | } 61 | } 62 | ] 63 | pipelineParams: 64 | - name: status_filter 65 | type: string 66 | description: The product status to filter by (e.g., "active"). 67 | ``` 68 | 69 | ## Reference 70 | 71 | | **field** | **type** | **required** | **description** | 72 | |:----------------|:---------|:-------------|:---------------------------------------------------------------------------------------------------------------| 73 | | kind | string | true | Must be `mongodb-aggregate`. | 74 | | source | string | true | The name of the `mongodb` source to use. | 75 | | description | string | true | A description of the tool that is passed to the LLM. | 76 | | database | string | true | The name of the MongoDB database containing the collection. | 77 | | collection | string | true | The name of the MongoDB collection to run the aggregation on. | 78 | | pipelinePayload | string | true | A JSON array of aggregation stage documents, provided as a string. Uses `{{json .param_name}}` for templating. | 79 | | pipelineParams | list | true | A list of parameter objects that define the variables used in the `pipelinePayload`. | 80 | | canonical | bool | false | Determines if the pipeline string is parsed using MongoDB's Canonical or Relaxed Extended JSON format. | 81 | | readOnly | bool | false | If `true`, the tool will fail if the pipeline contains write stages (`$out` or `$merge`). Defaults to `false`. | 82 | ``` -------------------------------------------------------------------------------- /.github/workflows/deploy_previous_version_docs.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: "Deploy Previous Version Docs" 16 | 17 | on: 18 | workflow_dispatch: 19 | inputs: 20 | version_tag: 21 | description: 'The old version tag to build docs for (e.g., v0.15.0)' 22 | required: true 23 | type: string 24 | 25 | jobs: 26 | build_and_deploy: 27 | runs-on: ubuntu-latest 28 | permissions: 29 | contents: write 30 | 31 | steps: 32 | - name: Checkout main branch (for latest templates and theme) 33 | uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 34 | with: 35 | ref: 'main' 36 | submodules: 'recursive' 37 | fetch-depth: 0 38 | 39 | - name: Checkout old content from tag into a temporary directory 40 | uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 41 | with: 42 | ref: ${{ github.event.inputs.version_tag }} 43 | path: 'old_version_source' # Checkout into a temp subdir 44 | # Sparse checkout to only get the content directory 45 | sparse-checkout: | 46 | docs 47 | 48 | - name: Replace content with old version 49 | run: | 50 | # Remove the current content directory from the main branch checkout 51 | rm -rf docs/ 52 | # Move the old content directory into place 53 | mv ./old_version_source/docs docs 54 | 55 | - name: Setup Hugo and Node 56 | uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3 57 | with: 58 | hugo-version: "0.145.0" 59 | extended: true 60 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 61 | with: 62 | node-version: "22" 63 | 64 | - name: Install Dependencies 65 | run: npm ci 66 | working-directory: .hugo 67 | 68 | - name: Build Hugo Site for Archived Version 69 | run: hugo --minify 70 | working-directory: .hugo 71 | env: 72 | HUGO_BASEURL: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/${{ github.event.inputs.version_tag }}/ 73 | HUGO_RELATIVEURLS: false 74 | 75 | - name: Deploy to gh-pages 76 | uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 77 | with: 78 | github_token: ${{ secrets.GITHUB_TOKEN }} 79 | publish_dir: .hugo/public 80 | publish_branch: versioned-gh-pages 81 | destination_dir: ./${{ github.event.inputs.version_tag }} 82 | keep_files: true 83 | allow_empty_commit: true 84 | commit_message: "docs(backport): deploy docs for ${{ github.event.inputs.version_tag }}" 85 | 86 | - name: Clean Build Directory 87 | run: rm -rf .hugo/public 88 | 89 | - name: Build Hugo Site 90 | run: hugo --minify 91 | working-directory: .hugo 92 | env: 93 | HUGO_BASEURL: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/ 94 | HUGO_RELATIVEURLS: false 95 | 96 | - name: Deploy to root 97 | uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 98 | with: 99 | github_token: ${{ secrets.GITHUB_TOKEN }} 100 | publish_dir: .hugo/public 101 | publish_branch: versioned-gh-pages 102 | keep_files: true 103 | allow_empty_commit: true 104 | commit_message: "deploy: docs to root for ${{ github.event.inputs.version_tag }}" ``` -------------------------------------------------------------------------------- /internal/sources/http/http.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package http 15 | 16 | import ( 17 | "context" 18 | "crypto/tls" 19 | "fmt" 20 | "net/http" 21 | "net/url" 22 | "time" 23 | 24 | "github.com/goccy/go-yaml" 25 | "github.com/googleapis/genai-toolbox/internal/sources" 26 | "github.com/googleapis/genai-toolbox/internal/util" 27 | "go.opentelemetry.io/otel/trace" 28 | ) 29 | 30 | const SourceKind string = "http" 31 | 32 | // validate interface 33 | var _ sources.SourceConfig = Config{} 34 | 35 | func init() { 36 | if !sources.Register(SourceKind, newConfig) { 37 | panic(fmt.Sprintf("source kind %q already registered", SourceKind)) 38 | } 39 | } 40 | 41 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) { 42 | actual := Config{Name: name, Timeout: "30s"} // Default timeout 43 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 44 | return nil, err 45 | } 46 | return actual, nil 47 | } 48 | 49 | type Config struct { 50 | Name string `yaml:"name" validate:"required"` 51 | Kind string `yaml:"kind" validate:"required"` 52 | BaseURL string `yaml:"baseUrl"` 53 | Timeout string `yaml:"timeout"` 54 | DefaultHeaders map[string]string `yaml:"headers"` 55 | QueryParams map[string]string `yaml:"queryParams"` 56 | DisableSslVerification bool `yaml:"disableSslVerification"` 57 | } 58 | 59 | func (r Config) SourceConfigKind() string { 60 | return SourceKind 61 | } 62 | 63 | // Initialize initializes an HTTP Source instance. 64 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { 65 | duration, err := time.ParseDuration(r.Timeout) 66 | if err != nil { 67 | return nil, fmt.Errorf("unable to parse Timeout string as time.Duration: %s", err) 68 | } 69 | 70 | tr := &http.Transport{} 71 | 72 | logger, err := util.LoggerFromContext(ctx) 73 | if err != nil { 74 | return nil, fmt.Errorf("unable to get logger from ctx: %s", err) 75 | } 76 | 77 | if r.DisableSslVerification { 78 | tr.TLSClientConfig = &tls.Config{ 79 | InsecureSkipVerify: true, 80 | } 81 | 82 | logger.WarnContext(ctx, "Insecure HTTP is enabled for HTTP source %s. TLS certificate verification is skipped.\n", r.Name) 83 | } 84 | 85 | client := http.Client{ 86 | Timeout: duration, 87 | Transport: tr, 88 | } 89 | 90 | // Validate BaseURL 91 | _, err = url.ParseRequestURI(r.BaseURL) 92 | if err != nil { 93 | return nil, fmt.Errorf("failed to parse BaseUrl %v", err) 94 | } 95 | 96 | ua, err := util.UserAgentFromContext(ctx) 97 | if err != nil { 98 | fmt.Printf("Error in User Agent retrieval: %s", err) 99 | } 100 | if r.DefaultHeaders == nil { 101 | r.DefaultHeaders = make(map[string]string) 102 | } 103 | if existingUA, ok := r.DefaultHeaders["User-Agent"]; ok { 104 | ua = ua + " " + existingUA 105 | } 106 | r.DefaultHeaders["User-Agent"] = ua 107 | 108 | s := &Source{ 109 | Name: r.Name, 110 | Kind: SourceKind, 111 | BaseURL: r.BaseURL, 112 | DefaultHeaders: r.DefaultHeaders, 113 | QueryParams: r.QueryParams, 114 | Client: &client, 115 | } 116 | return s, nil 117 | 118 | } 119 | 120 | var _ sources.Source = &Source{} 121 | 122 | type Source struct { 123 | Name string `yaml:"name"` 124 | Kind string `yaml:"kind"` 125 | BaseURL string `yaml:"baseUrl"` 126 | DefaultHeaders map[string]string `yaml:"headers"` 127 | QueryParams map[string]string `yaml:"queryParams"` 128 | Client *http.Client 129 | } 130 | 131 | func (s *Source) SourceKind() string { 132 | return SourceKind 133 | } 134 | ``` -------------------------------------------------------------------------------- /internal/sources/mssql/mssql_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mssql_test 16 | 17 | import ( 18 | "testing" 19 | 20 | yaml "github.com/goccy/go-yaml" 21 | "github.com/google/go-cmp/cmp" 22 | "github.com/googleapis/genai-toolbox/internal/server" 23 | "github.com/googleapis/genai-toolbox/internal/sources/mssql" 24 | "github.com/googleapis/genai-toolbox/internal/testutils" 25 | ) 26 | 27 | func TestParseFromYamlMssql(t *testing.T) { 28 | tcs := []struct { 29 | desc string 30 | in string 31 | want server.SourceConfigs 32 | }{ 33 | { 34 | desc: "basic example", 35 | in: ` 36 | sources: 37 | my-mssql-instance: 38 | kind: mssql 39 | host: 0.0.0.0 40 | port: my-port 41 | database: my_db 42 | user: my_user 43 | password: my_pass 44 | `, 45 | want: server.SourceConfigs{ 46 | "my-mssql-instance": mssql.Config{ 47 | Name: "my-mssql-instance", 48 | Kind: mssql.SourceKind, 49 | Host: "0.0.0.0", 50 | Port: "my-port", 51 | Database: "my_db", 52 | User: "my_user", 53 | Password: "my_pass", 54 | }, 55 | }, 56 | }, 57 | { 58 | desc: "with encrypt field", 59 | in: ` 60 | sources: 61 | my-mssql-instance: 62 | kind: mssql 63 | host: 0.0.0.0 64 | port: my-port 65 | database: my_db 66 | user: my_user 67 | password: my_pass 68 | encrypt: strict 69 | `, 70 | want: server.SourceConfigs{ 71 | "my-mssql-instance": mssql.Config{ 72 | Name: "my-mssql-instance", 73 | Kind: mssql.SourceKind, 74 | Host: "0.0.0.0", 75 | Port: "my-port", 76 | Database: "my_db", 77 | User: "my_user", 78 | Password: "my_pass", 79 | Encrypt: "strict", 80 | }, 81 | }, 82 | }, 83 | } 84 | for _, tc := range tcs { 85 | t.Run(tc.desc, func(t *testing.T) { 86 | got := struct { 87 | Sources server.SourceConfigs `yaml:"sources"` 88 | }{} 89 | // Parse contents 90 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 91 | if err != nil { 92 | t.Fatalf("unable to unmarshal: %s", err) 93 | } 94 | if !cmp.Equal(tc.want, got.Sources) { 95 | t.Fatalf("incorrect psarse: want %v, got %v", tc.want, got.Sources) 96 | } 97 | }) 98 | } 99 | } 100 | 101 | func TestFailParseFromYaml(t *testing.T) { 102 | tcs := []struct { 103 | desc string 104 | in string 105 | err string 106 | }{ 107 | { 108 | desc: "extra field", 109 | in: ` 110 | sources: 111 | my-mssql-instance: 112 | kind: mssql 113 | host: 0.0.0.0 114 | port: my-port 115 | database: my_db 116 | user: my_user 117 | password: my_pass 118 | foo: bar 119 | `, 120 | 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 | ", 121 | }, 122 | { 123 | desc: "missing required field", 124 | in: ` 125 | sources: 126 | my-mssql-instance: 127 | kind: mssql 128 | host: 0.0.0.0 129 | port: my-port 130 | database: my_db 131 | user: my_user 132 | `, 133 | err: "unable to parse source \"my-mssql-instance\" as \"mssql\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag", 134 | }, 135 | } 136 | for _, tc := range tcs { 137 | t.Run(tc.desc, func(t *testing.T) { 138 | got := struct { 139 | Sources server.SourceConfigs `yaml:"sources"` 140 | }{} 141 | // Parse contents 142 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 143 | if err == nil { 144 | t.Fatalf("expect parsing to fail") 145 | } 146 | errStr := err.Error() 147 | if errStr != tc.err { 148 | t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) 149 | } 150 | }) 151 | } 152 | } 153 | ``` -------------------------------------------------------------------------------- /internal/tools/mongodb/mongodbaggregate/mongodbaggregate_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mongodbaggregate_test 16 | 17 | import ( 18 | "strings" 19 | "testing" 20 | 21 | "github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbaggregate" 22 | 23 | yaml "github.com/goccy/go-yaml" 24 | "github.com/google/go-cmp/cmp" 25 | "github.com/googleapis/genai-toolbox/internal/server" 26 | "github.com/googleapis/genai-toolbox/internal/testutils" 27 | "github.com/googleapis/genai-toolbox/internal/tools" 28 | ) 29 | 30 | func TestParseFromYamlMongoQuery(t *testing.T) { 31 | ctx, err := testutils.ContextWithNewLogger() 32 | if err != nil { 33 | t.Fatalf("unexpected error: %s", err) 34 | } 35 | tcs := []struct { 36 | desc string 37 | in string 38 | want server.ToolConfigs 39 | }{ 40 | { 41 | desc: "basic example", 42 | in: ` 43 | tools: 44 | example_tool: 45 | kind: mongodb-aggregate 46 | source: my-instance 47 | description: some description 48 | database: test_db 49 | collection: test_coll 50 | readOnly: true 51 | pipelinePayload: | 52 | [{ $match: { name: {{json .name}} }}] 53 | pipelineParams: 54 | - name: name 55 | type: string 56 | description: small description 57 | `, 58 | want: server.ToolConfigs{ 59 | "example_tool": mongodbaggregate.Config{ 60 | Name: "example_tool", 61 | Kind: "mongodb-aggregate", 62 | Source: "my-instance", 63 | AuthRequired: []string{}, 64 | Database: "test_db", 65 | Collection: "test_coll", 66 | Description: "some description", 67 | PipelinePayload: "[{ $match: { name: {{json .name}} }}]\n", 68 | PipelineParams: tools.Parameters{ 69 | &tools.StringParameter{ 70 | CommonParameter: tools.CommonParameter{ 71 | Name: "name", 72 | Type: "string", 73 | Desc: "small description", 74 | }, 75 | }, 76 | }, 77 | ReadOnly: true, 78 | }, 79 | }, 80 | }, 81 | } 82 | for _, tc := range tcs { 83 | t.Run(tc.desc, func(t *testing.T) { 84 | got := struct { 85 | Tools server.ToolConfigs `yaml:"tools"` 86 | }{} 87 | // Parse contents 88 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 89 | if err != nil { 90 | t.Fatalf("unable to unmarshal: %s", err) 91 | } 92 | if diff := cmp.Diff(tc.want, got.Tools); diff != "" { 93 | t.Fatalf("incorrect parse: diff %v", diff) 94 | } 95 | }) 96 | } 97 | 98 | } 99 | 100 | func TestFailParseFromYamlMongoQuery(t *testing.T) { 101 | ctx, err := testutils.ContextWithNewLogger() 102 | if err != nil { 103 | t.Fatalf("unexpected error: %s", err) 104 | } 105 | tcs := []struct { 106 | desc string 107 | in string 108 | err string 109 | }{ 110 | { 111 | desc: "Invalid method", 112 | in: ` 113 | tools: 114 | example_tool: 115 | kind: mongodb-aggregate 116 | source: my-instance 117 | description: some description 118 | collection: test_coll 119 | pipelinePayload: | 120 | [{ $match: { name : {{json .name}} }}] 121 | `, 122 | err: `unable to parse tool "example_tool" as kind "mongodb-aggregate"`, 123 | }, 124 | } 125 | for _, tc := range tcs { 126 | t.Run(tc.desc, func(t *testing.T) { 127 | got := struct { 128 | Tools server.ToolConfigs `yaml:"tools"` 129 | }{} 130 | // Parse contents 131 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 132 | if err == nil { 133 | t.Fatalf("expect parsing to fail") 134 | } 135 | errStr := err.Error() 136 | if !strings.Contains(errStr, tc.err) { 137 | t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err) 138 | } 139 | }) 140 | } 141 | 142 | } 143 | ``` -------------------------------------------------------------------------------- /internal/sources/redis/redis_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package redis_test 16 | 17 | import ( 18 | "strings" 19 | "testing" 20 | 21 | yaml "github.com/goccy/go-yaml" 22 | "github.com/google/go-cmp/cmp" 23 | "github.com/googleapis/genai-toolbox/internal/server" 24 | "github.com/googleapis/genai-toolbox/internal/sources/redis" 25 | "github.com/googleapis/genai-toolbox/internal/testutils" 26 | ) 27 | 28 | func TestParseFromYamlRedis(t *testing.T) { 29 | tcs := []struct { 30 | desc string 31 | in string 32 | want server.SourceConfigs 33 | }{ 34 | { 35 | desc: "default setting", 36 | in: ` 37 | sources: 38 | my-redis-instance: 39 | kind: redis 40 | address: 41 | - 127.0.0.1 42 | `, 43 | want: server.SourceConfigs{ 44 | "my-redis-instance": redis.Config{ 45 | Name: "my-redis-instance", 46 | Kind: redis.SourceKind, 47 | Address: []string{"127.0.0.1"}, 48 | ClusterEnabled: false, 49 | UseGCPIAM: false, 50 | }, 51 | }, 52 | }, 53 | { 54 | desc: "advanced example", 55 | in: ` 56 | sources: 57 | my-redis-instance: 58 | kind: redis 59 | address: 60 | - 127.0.0.1 61 | password: my-pass 62 | database: 1 63 | useGCPIAM: true 64 | clusterEnabled: true 65 | `, 66 | want: server.SourceConfigs{ 67 | "my-redis-instance": redis.Config{ 68 | Name: "my-redis-instance", 69 | Kind: redis.SourceKind, 70 | Address: []string{"127.0.0.1"}, 71 | Password: "my-pass", 72 | Database: 1, 73 | ClusterEnabled: true, 74 | UseGCPIAM: true, 75 | }, 76 | }, 77 | }, 78 | } 79 | for _, tc := range tcs { 80 | t.Run(tc.desc, func(t *testing.T) { 81 | got := struct { 82 | Sources server.SourceConfigs `yaml:"sources"` 83 | }{} 84 | // Parse contents 85 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 86 | if err != nil { 87 | t.Fatalf("unable to unmarshal: %s", err) 88 | } 89 | if !cmp.Equal(tc.want, got.Sources) { 90 | t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources) 91 | } 92 | }) 93 | } 94 | 95 | } 96 | 97 | func TestFailParseFromYaml(t *testing.T) { 98 | tcs := []struct { 99 | desc string 100 | in string 101 | err string 102 | }{ 103 | { 104 | desc: "invalid database", 105 | in: ` 106 | sources: 107 | my-redis-instance: 108 | kind: redis 109 | project: my-project 110 | address: 111 | - 127.0.0.1 112 | password: my-pass 113 | database: data 114 | `, 115 | err: "cannot unmarshal string into Go struct field .Sources of type int", 116 | }, 117 | { 118 | desc: "extra field", 119 | in: ` 120 | sources: 121 | my-redis-instance: 122 | kind: redis 123 | project: my-project 124 | address: 125 | - 127.0.0.1 126 | password: my-pass 127 | database: 1 128 | `, 129 | err: "unable to parse source \"my-redis-instance\" as \"redis\": [6:1] unknown field \"project\"", 130 | }, 131 | { 132 | desc: "missing required field", 133 | in: ` 134 | sources: 135 | my-redis-instance: 136 | kind: redis 137 | `, 138 | err: "unable to parse source \"my-redis-instance\" as \"redis\": Key: 'Config.Address' Error:Field validation for 'Address' failed on the 'required' tag", 139 | }, 140 | } 141 | for _, tc := range tcs { 142 | t.Run(tc.desc, func(t *testing.T) { 143 | got := struct { 144 | Sources server.SourceConfigs `yaml:"sources"` 145 | }{} 146 | // Parse contents 147 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 148 | if err == nil { 149 | t.Fatalf("expect parsing to fail") 150 | } 151 | errStr := err.Error() 152 | if !strings.Contains(errStr, tc.err) { 153 | t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) 154 | } 155 | }) 156 | } 157 | } 158 | ``` -------------------------------------------------------------------------------- /.github/workflows/docs_preview_deploy.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: "docs" 16 | 17 | permissions: 18 | contents: write 19 | pull-requests: write 20 | 21 | # This Workflow depends on 'github.event.number', 22 | # not compatible with branch or manual triggers. 23 | on: 24 | pull_request: 25 | # Sync with github_actions_preview_fallback.yml on.pull_request.paths-ignore 26 | paths: 27 | - 'docs/**' 28 | - 'github/workflows/docs**' 29 | - '.hugo/**' 30 | pull_request_target: 31 | types: [labeled] 32 | paths: 33 | - 'docs/**' 34 | - 'github/workflows/docs**' 35 | - '.hugo/**' 36 | 37 | jobs: 38 | preview: 39 | # run job on proper workflow event triggers (skip job for pull_request event 40 | # from forks and only run pull_request_target for "docs: deploy-preview" 41 | # label) 42 | 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' }}" 43 | runs-on: ubuntu-24.04 44 | defaults: 45 | run: 46 | working-directory: .hugo 47 | concurrency: 48 | # Shared concurrency group wih preview cleanup. 49 | group: "preview-${{ github.event.number }}" 50 | cancel-in-progress: true 51 | steps: 52 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 53 | with: 54 | # Checkout the PR's HEAD commit (supports forks). 55 | ref: ${{ github.event.pull_request.head.sha }} 56 | fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod 57 | 58 | - name: Setup Hugo 59 | uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3 60 | with: 61 | hugo-version: "0.145.0" 62 | extended: true 63 | 64 | - name: Setup Node 65 | uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 66 | with: 67 | node-version: "22" 68 | 69 | - name: Cache dependencies 70 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 71 | with: 72 | path: ~/.npm 73 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 74 | restore-keys: | 75 | ${{ runner.os }}-node- 76 | 77 | - run: npm ci 78 | - run: hugo --minify 79 | env: 80 | HUGO_BASEURL: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/previews/PR-${{ github.event.number }}/ 81 | HUGO_ENVIRONMENT: preview 82 | HUGO_RELATIVEURLS: false 83 | 84 | - name: Deploy 85 | uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 86 | with: 87 | github_token: ${{ secrets.GITHUB_TOKEN }} 88 | publish_dir: .hugo/public 89 | publish_branch: versioned-gh-pages 90 | destination_dir: ./previews/PR-${{ github.event.number }} 91 | commit_message: "stage: PR-${{ github.event.number }}: ${{ github.event.head_commit.message }}" 92 | 93 | - name: Comment 94 | uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 95 | with: 96 | script: | 97 | github.rest.issues.createComment({ 98 | issue_number: context.payload.number, 99 | owner: context.repo.owner, 100 | repo: context.repo.repo, 101 | body: "🔎 Preview at https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/previews/PR-${{ github.event.number }}/" 102 | }) 103 | ``` -------------------------------------------------------------------------------- /internal/sources/clickhouse/clickhouse.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package clickhouse 16 | 17 | import ( 18 | "context" 19 | "database/sql" 20 | "fmt" 21 | "net/url" 22 | "time" 23 | 24 | _ "github.com/ClickHouse/clickhouse-go/v2" 25 | "github.com/goccy/go-yaml" 26 | "github.com/googleapis/genai-toolbox/internal/sources" 27 | "go.opentelemetry.io/otel/trace" 28 | ) 29 | 30 | const SourceKind string = "clickhouse" 31 | 32 | // validate interface 33 | var _ sources.SourceConfig = Config{} 34 | 35 | func init() { 36 | if !sources.Register(SourceKind, newConfig) { 37 | panic(fmt.Sprintf("source kind %q already registered", SourceKind)) 38 | } 39 | } 40 | 41 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) { 42 | actual := Config{Name: name} 43 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 44 | return nil, err 45 | } 46 | return actual, nil 47 | } 48 | 49 | type Config struct { 50 | Name string `yaml:"name" validate:"required"` 51 | Kind string `yaml:"kind" validate:"required"` 52 | Host string `yaml:"host" validate:"required"` 53 | Port string `yaml:"port" validate:"required"` 54 | Database string `yaml:"database" validate:"required"` 55 | User string `yaml:"user" validate:"required"` 56 | Password string `yaml:"password"` 57 | Protocol string `yaml:"protocol"` 58 | Secure bool `yaml:"secure"` 59 | } 60 | 61 | func (r Config) SourceConfigKind() string { 62 | return SourceKind 63 | } 64 | 65 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { 66 | pool, err := initClickHouseConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Database, r.Protocol, r.Secure) 67 | if err != nil { 68 | return nil, fmt.Errorf("unable to create pool: %w", err) 69 | } 70 | 71 | err = pool.PingContext(ctx) 72 | if err != nil { 73 | return nil, fmt.Errorf("unable to connect successfully: %w", err) 74 | } 75 | 76 | s := &Source{ 77 | Name: r.Name, 78 | Kind: SourceKind, 79 | Pool: pool, 80 | } 81 | return s, nil 82 | } 83 | 84 | var _ sources.Source = &Source{} 85 | 86 | type Source struct { 87 | Name string `yaml:"name"` 88 | Kind string `yaml:"kind"` 89 | Pool *sql.DB 90 | } 91 | 92 | func (s *Source) SourceKind() string { 93 | return SourceKind 94 | } 95 | 96 | func (s *Source) ClickHousePool() *sql.DB { 97 | return s.Pool 98 | } 99 | 100 | func validateConfig(protocol string) error { 101 | validProtocols := map[string]bool{"http": true, "https": true} 102 | 103 | if protocol != "" && !validProtocols[protocol] { 104 | return fmt.Errorf("invalid protocol: %s, must be one of: http, https", protocol) 105 | } 106 | return nil 107 | } 108 | 109 | func initClickHouseConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, pass, dbname, protocol string, secure bool) (*sql.DB, error) { 110 | //nolint:all // Reassigned ctx 111 | ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name) 112 | defer span.End() 113 | 114 | if protocol == "" { 115 | protocol = "https" 116 | } 117 | 118 | if err := validateConfig(protocol); err != nil { 119 | return nil, err 120 | } 121 | 122 | encodedUser := url.QueryEscape(user) 123 | encodedPass := url.QueryEscape(pass) 124 | 125 | var dsn string 126 | scheme := protocol 127 | if protocol == "http" && secure { 128 | scheme = "https" 129 | } 130 | dsn = fmt.Sprintf("%s://%s:%s@%s:%s/%s", scheme, encodedUser, encodedPass, host, port, dbname) 131 | if scheme == "https" { 132 | dsn += "?secure=true&skip_verify=false" 133 | } 134 | 135 | pool, err := sql.Open("clickhouse", dsn) 136 | if err != nil { 137 | return nil, fmt.Errorf("sql.Open: %w", err) 138 | } 139 | 140 | pool.SetMaxOpenConns(25) 141 | pool.SetMaxIdleConns(5) 142 | pool.SetConnMaxLifetime(5 * time.Minute) 143 | 144 | return pool, nil 145 | } 146 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/mongodb/mongodb-find-one.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "mongodb-find-one" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "mongodb-find-one" tool finds and retrieves a single document from a MongoDB collection. 7 | aliases: 8 | - /resources/tools/mongodb-find-one 9 | --- 10 | 11 | ## About 12 | 13 | A `mongodb-find-one` tool is used to retrieve the **first single document** that 14 | matches a specified filter from a MongoDB collection. If multiple documents 15 | match the filter, you can use `sort` options to control which document is 16 | returned. Otherwise, the selection is not guaranteed. 17 | 18 | The tool returns a single JSON object representing the document, wrapped in a 19 | JSON array. 20 | 21 | This tool is compatible with the following source kind: 22 | 23 | * [`mongodb`](../../sources/mongodb.md) 24 | 25 | --- 26 | 27 | ## Example 28 | 29 | Here's a common use case: finding a specific user by their unique email address 30 | and returning their profile information, while excluding sensitive fields like 31 | the password hash. 32 | 33 | ```yaml 34 | tools: 35 | get_user_profile: 36 | kind: mongodb-find-one 37 | source: my-mongo-source 38 | description: Retrieves a user's profile by their email address. 39 | database: user_data 40 | collection: profiles 41 | filterPayload: | 42 | { "email": {{json .email}} } 43 | filterParams: 44 | - name: email 45 | type: string 46 | description: The email address of the user to find. 47 | projectPayload: | 48 | { 49 | "password_hash": 0, 50 | "login_history": 0 51 | } 52 | ``` 53 | 54 | ## Reference 55 | 56 | | **field** | **type** | **required** | **description** | 57 | |:---------------|:---------|:-------------|:---------------------------------------------------------------------------------------------------------------------------------------------| 58 | | kind | string | true | Must be `mongodb-find-one`. | 59 | | source | string | true | The name of the `mongodb` source to use. | 60 | | description | string | true | A description of the tool that is passed to the LLM. | 61 | | database | string | true | The name of the MongoDB database to query. | 62 | | collection | string | true | The name of the MongoDB collection to query. | 63 | | filterPayload | string | true | The MongoDB query filter document to select the document. Uses `{{json .param_name}}` for templating. | 64 | | filterParams | list | true | A list of parameter objects that define the variables used in the `filterPayload`. | 65 | | projectPayload | string | false | An optional MongoDB projection document to specify which fields to include (1) or exclude (0) in the result. | 66 | | projectParams | list | false | A list of parameter objects for the `projectPayload`. | 67 | | sortPayload | string | false | An optional MongoDB sort document. Useful for selecting which document to return if the filter matches multiple (e.g., get the most recent). | 68 | | sortParams | list | false | A list of parameter objects for the `sortPayload`. | 69 | ```