Skip to content

Commit c9cde9f

Browse files
authored
Merge branch 'main' into fix-bigtable-resource-cleanup
2 parents a218a39 + 912aa9e commit c9cde9f

File tree

7 files changed

+169
-41
lines changed

7 files changed

+169
-41
lines changed

docs/BIGQUERY_README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export BIGQUERY_PROJECT="<your-gcp-project-id>"
7676
export BIGQUERY_LOCATION="<your-dataset-location>" # Optional
7777
export BIGQUERY_USE_CLIENT_OAUTH="true" # Optional: true, false, or a custom header name
7878
export BIGQUERY_SCOPES="<comma-separated-scopes>" # Optional
79+
export BIGQUERY_IMPERSONATE_SERVICE_ACCOUNT="<service-account-email>" # Optional: Service account to impersonate
7980
```
8081

8182
Add the following configuration to your MCP client (e.g., `settings.json` for Gemini CLI, `mcp_config.json` for Antigravity):

docs/en/documentation/configuration/embedding-models/_index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ kind: tool
7373
name: insert_embedding
7474
type: postgres-sql
7575
source: my-pg-instance
76+
description: Insert a new document into the database.
7677
statement: |
7778
INSERT INTO documents (content, embedding)
7879
VALUES ($1, $2);
@@ -92,6 +93,7 @@ kind: tool
9293
name: search_embedding
9394
type: postgres-sql
9495
source: my-pg-instance
96+
description: Search for documents in the database.
9597
statement: |
9698
SELECT id, content, embedding <-> $1 AS distance
9799
FROM documents

docs/en/documentation/configuration/embedding-models/gemini.md

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ title: "Gemini Embedding"
33
type: docs
44
weight: 1
55
description: >
6-
Use Google's Gemini models to generate high-performance text embeddings for vector databases.
6+
Use Google's Gemini models to generate high-performance text embeddings for
7+
vector databases.
78
---
89

910
## About
@@ -13,15 +14,17 @@ high-dimensional vectors.
1314

1415
### Authentication
1516

16-
Toolbox uses your [Application Default Credentials
17-
(ADC)][adc] to authorize with the
18-
Gemini API client.
17+
Toolbox supports two authentication modes:
1918

20-
Optionally, you can use an [API key][api-key] obtain an API
21-
Key from the [Google AI Studio][ai-studio].
19+
1. **Google AI (API Key):** Used if you
20+
provide `apiKey` (or set `GOOGLE_API_KEY`/`GEMINI_API_KEY` environment
21+
variables). This uses the [Google AI Studio][ai-studio] backend.
22+
2. **Vertex AI (ADC):** Used if provided `project` and `location` (or set
23+
`GOOGLE_CLOUD_PROJECT`/`GOOGLE_CLOUD_LOCATION` environment variables). This uses [Application
24+
Default Credentials (ADC)][adc].
2225

23-
We recommend using an API key for testing and using application default
24-
credentials for production.
26+
We recommend using an API key for quick testing and using Vertex AI with ADC for
27+
production environments.
2528

2629
[adc]: https://cloud.google.com/docs/authentication#adc
2730
[api-key]: https://ai.google.dev/gemini-api/docs/api-key#api-keys
@@ -41,14 +44,19 @@ to your database source.
4144
The `dimension` field must match the expected size of your database column
4245
(e.g., a `vector(768)` column in PostgreSQL). This setting is supported by newer
4346
models since 2024 only. You cannot set this value if using the earlier model
44-
(`models/embedding-001`). Check out [available Gemini models][modellist] for more
45-
information.
47+
(`models/embedding-001`). Check out [available Gemini models][modellist] for
48+
more information.
4649

4750
[modellist]:
48-
https://docs.cloud.google.com/vertex-ai/generative-ai/docs/embeddings/get-text-embeddings#supported-models
51+
https://docs.cloud.google.com/vertex-ai/generative-ai/docs/embeddings/get-text-embeddings#supported-models
4952

5053
## Example
5154

55+
### Using Google AI
56+
57+
Google AI uses API Key for authentication. You can get an API key from [Google
58+
AI Studio][ai-studio].
59+
5260
```yaml
5361
kind: embeddingModel
5462
name: gemini-model
@@ -58,16 +66,31 @@ apiKey: ${GOOGLE_API_KEY}
5866
dimension: 768
5967
```
6068
61-
{{< notice tip >}}
62-
Use environment variable replacement with the format ${ENV_NAME}
63-
instead of hardcoding your secrets into the configuration file.
69+
### Using Vertex AI
70+
71+
Vertex AI uses Application Default Credentials (ADC) for authentication. Learn
72+
how to set up ADC [here][adc].
73+
74+
```yaml
75+
kind: embeddingModel
76+
name: gemini-model
77+
type: gemini
78+
model: gemini-embedding-001
79+
project: ${GOOGLE_CLOUD_PROJECT}
80+
location: us-central1
81+
dimension: 768
82+
```
83+
84+
[adc]: https://docs.cloud.google.com/docs/authentication/provide-credentials-adc
85+
86+
{{< notice tip >}} Use environment variable replacement with the format
87+
${ENV_NAME} instead of hardcoding your secrets into the configuration file.
6488
{{< /notice >}}
6589
6690
## Reference
6791
68-
| **field** | **type** | **required** | **description** |
69-
|-----------|:--------:|:------------:|--------------------------------------------------------------|
70-
| type | string | true | Must be `gemini`. |
71-
| model | string | true | The Gemini model ID to use (e.g., `gemini-embedding-001`). |
72-
| apiKey | string | false | Your API Key from Google AI Studio. |
73-
| dimension | integer | false | The number of dimensions in the output vector (e.g., `768`). |
92+
| **field** | **type** | **required** | **description** |
93+
| ----------- | :------: | :----------: | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
94+
| type | string | true | Must be `gemini`. |
95+
| model | string | true | The Gemini model ID to use (e.g., `gemini-embedding-001`). |
96+
| dimension | integer | false | The number of dimensions in the output vector (e.g., `768`). |

docs/en/integrations/bigquery/prebuilt-configs/bigquery.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ description: "Details of the BigQuery prebuilt configuration."
1414
OAuth access token for authentication. Defaults to `false`.
1515
* `BIGQUERY_SCOPES`: (Optional) A comma-separated list of OAuth scopes to
1616
use for authentication.
17+
* `BIGQUERY_IMPERSONATE_SERVICE_ACCOUNT`: (Optional) Service account email
18+
to impersonate when making BigQuery and Dataplex API calls. The
19+
authenticated principal must have `roles/iam.serviceAccountTokenCreator`
20+
on the target service account.
1721
* **Permissions:**
1822
* **BigQuery User** (`roles/bigquery.user`) to execute queries and view
1923
metadata.

internal/embeddingmodels/gemini/gemini.go

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"context"
1919
"fmt"
2020
"net/http"
21+
"os"
2122

2223
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
2324
"github.com/googleapis/genai-toolbox/internal/util"
@@ -34,6 +35,8 @@ type Config struct {
3435
Type string `yaml:"type" validate:"required"`
3536
Model string `yaml:"model" validate:"required"`
3637
ApiKey string `yaml:"apiKey"`
38+
Project string `yaml:"project"`
39+
Location string `yaml:"location"`
3740
Dimension int32 `yaml:"dimension"`
3841
}
3942

@@ -44,12 +47,60 @@ func (cfg Config) EmbeddingModelConfigType() string {
4447

4548
// Initialize a Gemini embedding model
4649
func (cfg Config) Initialize(ctx context.Context) (embeddingmodels.EmbeddingModel, error) {
47-
// Get client configs
4850
configs := &genai.ClientConfig{}
49-
if cfg.ApiKey != "" {
50-
configs.APIKey = cfg.ApiKey
51+
52+
// Retrieve logger from context
53+
l, err := util.LoggerFromContext(ctx)
54+
if err != nil {
55+
return nil, fmt.Errorf("unable to retrieve logger: %w", err)
56+
}
57+
58+
// Get API Key
59+
apiKey := cfg.ApiKey
60+
if apiKey == "" {
61+
apiKey = os.Getenv("GOOGLE_API_KEY")
62+
}
63+
if apiKey == "" {
64+
apiKey = os.Getenv("GEMINI_API_KEY")
65+
}
66+
67+
// Try to resolve Project and Location
68+
project := cfg.Project
69+
if project == "" {
70+
project = os.Getenv("GOOGLE_CLOUD_PROJECT")
71+
}
72+
73+
location := cfg.Location
74+
if location == "" {
75+
location = os.Getenv("GOOGLE_CLOUD_LOCATION")
5176
}
5277

78+
// Determine the Backend
79+
if project != "" && location != "" {
80+
// VertexAI API uses ADC for authentication.
81+
// ADC requires `Project` and `Location` to be set.
82+
configs.Backend = genai.BackendVertexAI
83+
configs.Project = project
84+
configs.Location = location
85+
86+
l.InfoContext(ctx, "Using Vertex AI backend for Gemini embedding", "project", project, "location", location)
87+
88+
} else if apiKey != "" {
89+
// Using Gemini API, which uses API Key for authentication.
90+
configs.Backend = genai.BackendGeminiAPI
91+
configs.APIKey = apiKey
92+
93+
l.InfoContext(ctx, "Using Google AI (Gemini API) backend for Gemini embedding")
94+
95+
} else {
96+
// Missing credentials
97+
return nil, fmt.Errorf("missing credentials for Gemini embedding: " +
98+
"For Google AI: Provide 'apiKey' in YAML or set GOOGLE_API_KEY/GEMINI_API_KEY env vars. " +
99+
"For Vertex AI: Provide 'project'/'location' in YAML or via GOOGLE_CLOUD_PROJECT/GOOGLE_CLOUD_LOCATION env vars. " +
100+
"See documentation for details: https://googleapis.github.io/genai-toolbox/resources/embeddingmodels/gemini/")
101+
}
102+
103+
// Set user agent
53104
ua, err := util.UserAgentFromContext(ctx)
54105
if err != nil {
55106
return nil, fmt.Errorf("failed to get user agent from context: %w", err)
@@ -63,14 +114,13 @@ func (cfg Config) Initialize(ctx context.Context) (embeddingmodels.EmbeddingMode
63114
// Create new Gemini API client
64115
client, err := genai.NewClient(ctx, configs)
65116
if err != nil {
66-
return nil, fmt.Errorf("unable to create Gemini API client")
117+
return nil, fmt.Errorf("unable to create Gemini API client: %w", err)
67118
}
68119

69-
m := &EmbeddingModel{
120+
return &EmbeddingModel{
70121
Config: cfg,
71122
Client: client,
72-
}
73-
return m, nil
123+
}, nil
74124
}
75125

76126
var _ embeddingmodels.EmbeddingModel = EmbeddingModel{}

internal/embeddingmodels/gemini/gemini_test.go

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package gemini_test
1616

1717
import (
1818
"context"
19+
"strings"
1920
"testing"
2021

2122
"github.com/google/go-cmp/cmp"
@@ -37,36 +38,58 @@ func TestParseFromYamlGemini(t *testing.T) {
3738
kind: embeddingModel
3839
name: my-gemini-model
3940
type: gemini
40-
model: text-embedding-004
41+
model: gemini-embedding-001
4142
`,
4243
want: map[string]embeddingmodels.EmbeddingModelConfig{
4344
"my-gemini-model": gemini.Config{
4445
Name: "my-gemini-model",
4546
Type: gemini.EmbeddingModelType,
46-
Model: "text-embedding-004",
47+
Model: "gemini-embedding-001",
4748
},
4849
},
4950
},
5051
{
51-
desc: "full example with optional fields",
52+
desc: "full example with Google AI fields",
5253
in: `
5354
kind: embeddingModel
5455
name: complex-gemini
5556
type: gemini
56-
model: text-embedding-004
57+
model: gemini-embedding-001
5758
apiKey: "test-api-key"
5859
dimension: 768
5960
`,
6061
want: map[string]embeddingmodels.EmbeddingModelConfig{
6162
"complex-gemini": gemini.Config{
6263
Name: "complex-gemini",
6364
Type: gemini.EmbeddingModelType,
64-
Model: "text-embedding-004",
65+
Model: "gemini-embedding-001",
6566
ApiKey: "test-api-key",
6667
Dimension: 768,
6768
},
6869
},
6970
},
71+
{
72+
desc: "Vertex AI configuration",
73+
in: `
74+
kind: embeddingModel
75+
name: vertex-gemini
76+
type: gemini
77+
model: gemini-embedding-001
78+
project: "my-project"
79+
location: "us-central1"
80+
dimension: 512
81+
`,
82+
want: map[string]embeddingmodels.EmbeddingModelConfig{
83+
"vertex-gemini": gemini.Config{
84+
Name: "vertex-gemini",
85+
Type: gemini.EmbeddingModelType,
86+
Model: "gemini-embedding-001",
87+
Project: "my-project",
88+
Location: "us-central1",
89+
Dimension: 512,
90+
},
91+
},
92+
},
7093
}
7194
for _, tc := range tcs {
7295
t.Run(tc.desc, func(t *testing.T) {
@@ -81,6 +104,7 @@ func TestParseFromYamlGemini(t *testing.T) {
81104
})
82105
}
83106
}
107+
84108
func TestFailParseFromYamlGemini(t *testing.T) {
85109
tcs := []struct {
86110
desc string
@@ -94,7 +118,6 @@ func TestFailParseFromYamlGemini(t *testing.T) {
94118
name: bad-model
95119
type: gemini
96120
`,
97-
// Removed the specific model name from the prefix to match your output
98121
err: "error unmarshaling embeddingModel: unable to parse as \"bad-model\": Key: 'Config.Model' Error:Field validation for 'Model' failed on the 'required' tag",
99122
},
100123
{
@@ -103,21 +126,45 @@ func TestFailParseFromYamlGemini(t *testing.T) {
103126
kind: embeddingModel
104127
name: bad-field
105128
type: gemini
106-
model: text-embedding-004
129+
model: gemini-embedding-001
107130
invalid_param: true
108131
`,
109-
// Updated to match the specific line-starting format of your error output
110-
err: "error unmarshaling embeddingModel: unable to parse as \"bad-field\": [1:1] unknown field \"invalid_param\"\n> 1 | invalid_param: true\n ^\n 2 | model: text-embedding-004\n 3 | name: bad-field\n 4 | type: gemini",
132+
err: "error unmarshaling embeddingModel: unable to parse as \"bad-field\": [1:1] unknown field \"invalid_param\"\n> 1 | invalid_param: true\n ^\n 2 | model: gemini-embedding-001\n 3 | name: bad-field\n 4 | type: gemini",
133+
},
134+
{
135+
desc: "missing both Vertex and Google AI credentials",
136+
in: `
137+
kind: embeddingModel
138+
name: missing-creds
139+
type: gemini
140+
model: text-embedding-004
141+
`,
142+
err: "unable to initialize embedding model \"missing-creds\": missing credentials for Gemini embedding: For Google AI: Provide 'apiKey' in YAML or set GOOGLE_API_KEY/GEMINI_API_KEY env vars. For Vertex AI: Provide 'project'/'location' in YAML or via GOOGLE_CLOUD_PROJECT/GOOGLE_CLOUD_LOCATION env vars. See documentation for details: https://googleapis.github.io/genai-toolbox/resources/embeddingmodels/gemini/",
111143
},
112144
}
113145
for _, tc := range tcs {
114146
t.Run(tc.desc, func(t *testing.T) {
115-
_, _, _, _, _, _, err := server.UnmarshalResourceConfig(context.Background(), testutils.FormatYaml(tc.in))
116-
if err == nil {
117-
t.Fatalf("expect parsing to fail")
147+
t.Setenv("GOOGLE_API_KEY", "")
148+
t.Setenv("GEMINI_API_KEY", "")
149+
t.Setenv("GOOGLE_CLOUD_PROJECT", "")
150+
t.Setenv("GOOGLE_CLOUD_LOCATION", "")
151+
152+
_, embeddingConfigs, _, _, _, _, err := server.UnmarshalResourceConfig(context.Background(), testutils.FormatYaml(tc.in))
153+
if err != nil {
154+
if err.Error() != tc.err {
155+
t.Fatalf("unexpected unmarshal error:\ngot: %q\nwant: %q", err.Error(), tc.err)
156+
}
157+
return
118158
}
119-
if err.Error() != tc.err {
120-
t.Fatalf("unexpected error:\ngot: %q\nwant: %q", err.Error(), tc.err)
159+
160+
for _, cfg := range embeddingConfigs {
161+
_, err = cfg.Initialize()
162+
if err == nil {
163+
t.Fatalf("expect initialization to fail for case: %s", tc.desc)
164+
}
165+
if !strings.Contains(err.Error(), tc.err) {
166+
t.Fatalf("unexpected init error:\ngot: %q\nwant: %q", err.Error(), tc.err)
167+
}
121168
}
122169
})
123170
}

internal/prebuiltconfigs/tools/bigquery.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ sources:
2020
useClientOAuth: ${BIGQUERY_USE_CLIENT_OAUTH:false}
2121
scopes: ${BIGQUERY_SCOPES:}
2222
maxQueryResultRows: ${BIGQUERY_MAX_QUERY_RESULT_ROWS:50}
23+
impersonateServiceAccount: ${BIGQUERY_IMPERSONATE_SERVICE_ACCOUNT:}
2324
tools:
2425
analyze_contribution:
2526
kind: bigquery-analyze-contribution

0 commit comments

Comments
 (0)