fix(ai): support GOOGLE_CLOUD_API_KEY for google-vertex (#1948) (#1976)

This commit is contained in:
Gordon Hui
2026-03-09 20:30:22 +08:00
committed by GitHub
parent d9cfa115be
commit 01f7faae9c
4 changed files with 51 additions and 12 deletions

View File

@@ -37,7 +37,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an
- [Environment Variables](#environment-variables-nodejs-only)
- [Checking Environment Variables](#checking-environment-variables)
- [OAuth Providers](#oauth-providers)
- [Vertex AI (ADC)](#vertex-ai-adc)
- [Vertex AI](#vertex-ai)
- [CLI Login](#cli-login)
- [Programmatic OAuth](#programmatic-oauth)
- [Login Flow Example](#login-flow-example)
@@ -907,7 +907,7 @@ In Node.js environments, you can set environment variables to avoid passing API
| Azure OpenAI | `AZURE_OPENAI_API_KEY` + `AZURE_OPENAI_BASE_URL` or `AZURE_OPENAI_RESOURCE_NAME` (optional `AZURE_OPENAI_API_VERSION`, `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` like `model=deployment,model2=deployment2`) |
| Anthropic | `ANTHROPIC_API_KEY` or `ANTHROPIC_OAUTH_TOKEN` |
| Google | `GEMINI_API_KEY` |
| Vertex AI | `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) + `GOOGLE_CLOUD_LOCATION` + ADC |
| Vertex AI | `GOOGLE_CLOUD_API_KEY` or `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) + `GOOGLE_CLOUD_LOCATION` + ADC |
| Mistral | `MISTRAL_API_KEY` |
| Groq | `GROQ_API_KEY` |
| Cerebras | `CEREBRAS_API_KEY` |
@@ -975,14 +975,15 @@ Several providers require OAuth authentication instead of static API keys:
For paid Cloud Code Assist subscriptions, set `GOOGLE_CLOUD_PROJECT` or `GOOGLE_CLOUD_PROJECT_ID` to your project ID.
### Vertex AI (ADC)
### Vertex AI
Vertex AI models use Application Default Credentials (ADC):
Vertex AI models support either a Google Cloud API key or Application Default Credentials (ADC):
- **Local development**: Run `gcloud auth application-default login`
- **CI/Production**: Set `GOOGLE_APPLICATION_CREDENTIALS` to point to a service account JSON key file
- **API key**: Set `GOOGLE_CLOUD_API_KEY` or pass `apiKey` in the call options.
- **Local development (ADC)**: Run `gcloud auth application-default login`
- **CI/Production (ADC)**: Set `GOOGLE_APPLICATION_CREDENTIALS` to point to a service account JSON key file
Also set `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) and `GOOGLE_CLOUD_LOCATION`. You can also pass `project`/`location` in the call options.
When using ADC, also set `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) and `GOOGLE_CLOUD_LOCATION`. You can also pass `project`/`location` in the call options. When using `GOOGLE_CLOUD_API_KEY`, `project` and `location` are not required.
Example:
@@ -1003,6 +1004,8 @@ import { getModel, complete } from '@mariozechner/pi-ai';
const model = getModel('google-vertex', 'gemini-2.5-flash');
const response = await complete(model, {
messages: [{ role: 'user', content: 'Hello from Vertex AI' }]
}, {
apiKey: process.env.GOOGLE_CLOUD_API_KEY,
});
for (const block of response.content) {

View File

@@ -73,9 +73,13 @@ export function getEnvApiKey(provider: any): string | undefined {
return process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;
}
// Vertex AI uses Application Default Credentials, not API keys.
// Auth is configured via `gcloud auth application-default login`.
// Vertex AI supports either an explicit API key or Application Default Credentials
// Auth is configured via `gcloud auth application-default login`
if (provider === "google-vertex") {
if (process.env.GOOGLE_CLOUD_API_KEY) {
return process.env.GOOGLE_CLOUD_API_KEY;
}
const hasCredentials = hasVertexAdcCredentials();
const hasProject = !!(process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT);
const hasLocation = !!process.env.GOOGLE_CLOUD_LOCATION;

View File

@@ -84,9 +84,11 @@ export const streamGoogleVertex: StreamFunction<"google-vertex", GoogleVertexOpt
};
try {
const project = resolveProject(options);
const location = resolveLocation(options);
const client = createClient(model, project, location, options?.headers);
const apiKey = resolveApiKey(options);
// Create the client using either a Vertex API key, if provided, or ADC with project and location
const client = apiKey
? createClientWithApiKey(model, apiKey, options?.headers)
: createClient(model, resolveProject(options), resolveLocation(options), options?.headers);
let params = buildParams(model, context, options);
const nextParams = await options?.onPayload?.(params, model);
if (nextParams !== undefined) {
@@ -341,6 +343,31 @@ function createClient(
});
}
function createClientWithApiKey(
model: Model<"google-vertex">,
apiKey: string,
optionsHeaders?: Record<string, string>,
): GoogleGenAI {
const httpOptions: { headers?: Record<string, string> } = {};
if (model.headers || optionsHeaders) {
httpOptions.headers = { ...model.headers, ...optionsHeaders };
}
const hasHttpOptions = Object.values(httpOptions).some(Boolean);
return new GoogleGenAI({
vertexai: true,
apiKey,
apiVersion: API_VERSION,
httpOptions: hasHttpOptions ? httpOptions : undefined,
});
}
function resolveApiKey(options?: GoogleVertexOptions): string | undefined {
return options?.apiKey || process.env.GOOGLE_CLOUD_API_KEY;
}
function resolveProject(options?: GoogleVertexOptions): string {
const project = options?.project || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
if (!project) {

View File

@@ -380,6 +380,7 @@ describe("Generate E2E Tests", () => {
describe("Google Vertex Provider (gemini-3-flash-preview)", () => {
const vertexProject = process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
const vertexLocation = process.env.GOOGLE_CLOUD_LOCATION;
const vertexApiKey = process.env.GOOGLE_CLOUD_API_KEY;
const isVertexConfigured = Boolean(vertexProject && vertexLocation);
const vertexOptions = { project: vertexProject, location: vertexLocation } as const;
const llm = getModel("google-vertex", "gemini-3-flash-preview");
@@ -388,6 +389,10 @@ describe("Generate E2E Tests", () => {
await basicTextGeneration(llm, vertexOptions);
});
it.skipIf(!vertexApiKey)("should complete basic text generation with Vertex API key", { retry: 3 }, async () => {
await basicTextGeneration(llm, { apiKey: vertexApiKey! });
});
it.skipIf(!isVertexConfigured)("should handle tool calling", { retry: 3 }, async () => {
await handleToolCall(llm, vertexOptions);
});