diff --git a/core/src/models/apigee_llm.ts b/core/src/models/apigee_llm.ts index 45675f838..480354d72 100644 --- a/core/src/models/apigee_llm.ts +++ b/core/src/models/apigee_llm.ts @@ -40,10 +40,10 @@ export interface ApigeeLlmParams extends GeminiParams { */ proxyUrl?: string; /** - * API key to use. If not provided, it will look for - * the GOOGLE_GENAI_API_KEY or GEMINI_API_KEY environment variable. If gemini - * provider is selected and no key is provided, the fake key "-" will be - * used for the "x-goog-api-key" header. + * API key to use. If not provided, it will look for the + * GOOGLE_GENAI_API_KEY, GOOGLE_API_KEY or GEMINI_API_KEY environment + * variable, in that order. If gemini provider is selected and no key is + * provided, the fake key "-" will be used for the "x-goog-api-key" header. */ apiKey?: string; } diff --git a/core/src/models/google_llm.ts b/core/src/models/google_llm.ts index 0bc30049e..cac07d4a7 100644 --- a/core/src/models/google_llm.ts +++ b/core/src/models/google_llm.ts @@ -36,7 +36,8 @@ export interface GeminiParams { model?: string; /** * The API key to use for the Gemini API. If not provided, it will look for - * the GOOGLE_GENAI_API_KEY or GEMINI_API_KEY environment variable. + * the GOOGLE_GENAI_API_KEY, GOOGLE_API_KEY or GEMINI_API_KEY environment + * variable, in that order. */ apiKey?: string; /** @@ -117,7 +118,7 @@ export class Gemini extends BaseLlm { }); if (!params.vertexai && !params.apiKey) { throw new Error( - 'API key must be provided via constructor or GOOGLE_GENAI_API_KEY or GEMINI_API_KEY environment variable.', + 'API key must be provided via constructor or GOOGLE_GENAI_API_KEY, GOOGLE_API_KEY or GEMINI_API_KEY environment variable.', ); } this.project = params.project; @@ -395,8 +396,12 @@ export function geminiInitParams({ } } else { if (!params.apiKey && !isBrowser()) { + // GOOGLE_API_KEY is what `adk create` writes and what @google/genai + // itself reads; it precedes GEMINI_API_KEY to match the SDK's ordering. params.apiKey = - process.env['GOOGLE_GENAI_API_KEY'] || process.env['GEMINI_API_KEY']; + process.env['GOOGLE_GENAI_API_KEY'] || + process.env['GOOGLE_API_KEY'] || + process.env['GEMINI_API_KEY']; } } return params; diff --git a/core/test/models/google_llm_test.ts b/core/test/models/google_llm_test.ts index 1d745c403..b96992017 100644 --- a/core/test/models/google_llm_test.ts +++ b/core/test/models/google_llm_test.ts @@ -56,6 +56,7 @@ describe('GoogleLlm', () => { delete process.env['GOOGLE_CLOUD_PROJECT']; delete process.env['GOOGLE_CLOUD_LOCATION']; delete process.env['GOOGLE_GENAI_API_KEY']; + delete process.env['GOOGLE_API_KEY']; delete process.env['GEMINI_API_KEY']; delete process.env['GOOGLE_GENAI_USE_VERTEXAI']; delete process.env['GOOGLE_CLOUD_AGENT_ENGINE_ID']; @@ -66,7 +67,21 @@ describe('GoogleLlm', () => { it('should throw error if apiKey is missing in constructor', () => { expect(() => new TestGemini({model: 'gemini-1.5-flash'})).toThrow( - /API key must be provided/, + /API key must be provided via constructor or GOOGLE_GENAI_API_KEY, GOOGLE_API_KEY or GEMINI_API_KEY environment variable\./, + ); + }); + + it('should construct from the .env written by `adk create`', () => { + // The exact pair of lines generateEnvFile() emits for a Gemini API key in + // dev/src/cli/cli_create.ts. + process.env['GOOGLE_API_KEY'] = 'cli-key'; + process.env['GOOGLE_GENAI_USE_VERTEXAI'] = '0'; + + const llm = new TestGemini({model: 'gemini-1.5-flash'}); + + expect(llm.apiClient.vertexai).toBe(false); + expect(vi.mocked(GoogleGenAI)).toHaveBeenLastCalledWith( + expect.objectContaining({apiKey: 'cli-key'}), ); }); @@ -294,6 +309,69 @@ describe('GoogleLlm', () => { expect(params.apiKey).toBeUndefined(); }); + // GOOGLE_API_KEY sits ahead of GEMINI_API_KEY to match the ordering + // @google/genai itself applies in getApiKeyFromEnv(). + const apiKeyEnvPrecedence: Array<{ + description: string; + env: Record; + expected: string; + }> = [ + { + description: 'GOOGLE_API_KEY when it is the only key set', + env: {'GOOGLE_API_KEY': 'google-api-key'}, + expected: 'google-api-key', + }, + { + description: 'GOOGLE_GENAI_API_KEY over GOOGLE_API_KEY', + env: { + 'GOOGLE_GENAI_API_KEY': 'genai-api-key', + 'GOOGLE_API_KEY': 'google-api-key', + }, + expected: 'genai-api-key', + }, + { + description: 'GOOGLE_API_KEY over GEMINI_API_KEY', + env: { + 'GOOGLE_API_KEY': 'google-api-key', + 'GEMINI_API_KEY': 'gemini-api-key', + }, + expected: 'google-api-key', + }, + { + description: 'GEMINI_API_KEY when it is the only key set', + env: {'GEMINI_API_KEY': 'gemini-api-key'}, + expected: 'gemini-api-key', + }, + ]; + + apiKeyEnvPrecedence.forEach(({description, env, expected}) => { + it(`should resolve ${description}`, () => { + Object.assign(process.env, env); + const params = geminiInitParams({model: 'gemini-1.5-flash'}); + expect(params.apiKey).toBe(expected); + }); + }); + + it('should prefer the constructor apiKey over GOOGLE_API_KEY', () => { + process.env['GOOGLE_API_KEY'] = 'google-api-key'; + const params = geminiInitParams({ + model: 'gemini-1.5-flash', + apiKey: 'explicit-key', + }); + expect(params.apiKey).toBe('explicit-key'); + }); + + it('should not use GOOGLE_API_KEY on the Vertex AI path', () => { + process.env['GOOGLE_API_KEY'] = 'google-api-key'; + const params = geminiInitParams({ + model: 'gemini-1.5-flash', + vertexai: true, + project: 'test-project', + location: 'us-central1', + }); + expect(params.apiKey).toBeUndefined(); + }); + it('should initialize params for Vertex AI', () => { const input = { model: 'gemini-1.5-flash', diff --git a/dev/test/cli/cli_create_test.ts b/dev/test/cli/cli_create_test.ts index d903516cd..b1520f901 100644 --- a/dev/test/cli/cli_create_test.ts +++ b/dev/test/cli/cli_create_test.ts @@ -139,6 +139,10 @@ describe('createAgent', () => { apiKey: 'my-api-key', }); + // Pins the API key env var name `adk create` writes. It MUST stay one of + // the names geminiInitParams() resolves in core/src/models/google_llm.ts, + // or the scaffolded agent cannot construct its own model; see the core + // test 'should construct from the .env written by `adk create`'. expect(saveToFile).toHaveBeenCalledWith( expect.stringContaining('.env'), expect.stringContaining('GOOGLE_API_KEY=my-api-key'),