Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions core/src/main/java/com/google/adk/config/AdkConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.adk.config;

import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
* Central configuration provider for the ADK SDK.
*
* <p>Resolves configuration values using the following fallback chain (highest priority first):
*
* <ol>
* <li>Programmatic values set via {@link #set(String, String)}.
* <li>JVM system properties ({@link System#getProperty(String)}).
* <li>Operating system environment variables ({@link System#getenv(String)}) — preserved for
* backward compatibility.
* </ol>
*
* <p>This abstraction allows applications (e.g. Spring Boot) to inject configuration that
* originates from {@code application.yaml}, secret managers, or any other source, without relying
* on the immutable OS environment.
*/
public final class AdkConfiguration {

private static final ConcurrentMap<String, String> OVERRIDES = new ConcurrentHashMap<>();

private AdkConfiguration() {}

/**
* Programmatically sets a configuration value. Takes precedence over system properties and
* environment variables.
*
* @param key the configuration key (typically the same name as the legacy environment variable)
* @param value the value to associate with the key; must not be {@code null}. Use {@link
* #clear(String)} to remove an entry.
*/
public static void set(String key, String value) {
if (key == null) {
throw new IllegalArgumentException("key must not be null");
}
if (value == null) {
throw new IllegalArgumentException("value must not be null; use clear(key) to remove");
}
OVERRIDES.put(key, value);
}

/** Removes a programmatic override for the given key, if any. */
public static void clear(String key) {
if (key != null) {
OVERRIDES.remove(key);
}
}

/** Removes all programmatic overrides. Primarily intended for tests. */
public static void clearAll() {
OVERRIDES.clear();
}

/**
* Resolves a configuration value using the fallback chain described in the class javadoc.
*
* @param key the configuration key
* @return an {@link Optional} containing the resolved value, or empty if no source provides one
*/
public static Optional<String> get(String key) {
if (key == null) {
return Optional.empty();
}
String override = OVERRIDES.get(key);
if (override != null) {
return Optional.of(override);
}
String systemProperty = System.getProperty(key);
if (systemProperty != null) {
return Optional.of(systemProperty);
}
return Optional.ofNullable(System.getenv(key));
}

/**
* Resolves a configuration value, returning the provided default if no source provides one.
*
* @param key the configuration key
* @param defaultValue value to return when the key cannot be resolved
*/
public static String getOrDefault(String key, String defaultValue) {
return get(key).orElse(defaultValue);
}
}
5 changes: 3 additions & 2 deletions core/src/main/java/com/google/adk/models/ApigeeLlm.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.google.common.base.Strings.isNullOrEmpty;

import com.google.adk.Version;
import com.google.adk.config.AdkConfiguration;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
Expand Down Expand Up @@ -73,7 +74,7 @@ private ApigeeLlm(String modelName, String proxyUrl, Map<String, String> customH

String effectiveProxyUrl = proxyUrl;
if (isNullOrEmpty(effectiveProxyUrl)) {
effectiveProxyUrl = System.getenv(APIGEE_PROXY_URL_ENV_VARIABLE_NAME);
effectiveProxyUrl = AdkConfiguration.get(APIGEE_PROXY_URL_ENV_VARIABLE_NAME).orElse(null);
}
if (isNullOrEmpty(effectiveProxyUrl)) {
throw new IllegalArgumentException(
Expand Down Expand Up @@ -306,7 +307,7 @@ private static boolean validateModelString(String model) {
}

private static boolean isEnvEnabled(String envVarName) {
String value = System.getenv(envVarName);
String value = AdkConfiguration.get(envVarName).orElse(null);
return Boolean.parseBoolean(value) || Objects.equals(value, "1");
}

Expand Down
9 changes: 6 additions & 3 deletions core/src/main/java/com/google/adk/sessions/ApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static com.google.common.base.StandardSystemProperty.JAVA_VERSION;

import com.google.adk.config.AdkConfiguration;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.base.Ascii;
import com.google.common.base.Strings;
Expand Down Expand Up @@ -46,7 +47,7 @@ abstract class ApiClient {
/** Constructs an ApiClient for Google AI APIs. */
ApiClient(@Nullable String apiKey, @Nullable HttpOptions customHttpOptions) {

this.apiKey = apiKey != null ? apiKey : System.getenv("GOOGLE_API_KEY");
this.apiKey = apiKey != null ? apiKey : AdkConfiguration.get("GOOGLE_API_KEY").orElse(null);

if (Strings.isNullOrEmpty(this.apiKey)) {
throw new IllegalArgumentException(
Expand Down Expand Up @@ -74,15 +75,17 @@ abstract class ApiClient {
@Nullable GoogleCredentials credentials,
@Nullable HttpOptions customHttpOptions) {

this.project = project != null ? project : System.getenv("GOOGLE_CLOUD_PROJECT");
this.project =
project != null ? project : AdkConfiguration.get("GOOGLE_CLOUD_PROJECT").orElse(null);

if (Strings.isNullOrEmpty(this.project)) {
throw new IllegalArgumentException(
"Project must either be provided or set in the environment variable"
+ " GOOGLE_CLOUD_PROJECT.");
}

this.location = location != null ? location : System.getenv("GOOGLE_CLOUD_LOCATION");
this.location =
location != null ? location : AdkConfiguration.get("GOOGLE_CLOUD_LOCATION").orElse(null);

if (Strings.isNullOrEmpty(this.location)) {
throw new IllegalArgumentException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.adk.config.AdkConfiguration;
import com.google.adk.models.LlmRequest;
import com.google.adk.tools.ToolContext;
import com.google.adk.utils.ModelNameUtils;
Expand Down Expand Up @@ -106,7 +107,8 @@ public Completable processLlmRequest(
LlmRequest.Builder llmRequestBuilder, ToolContext toolContext) {
LlmRequest llmRequest = llmRequestBuilder.build();
// Use Gemini built-in Vertex AI RAG tool for Gemini models when using Vertex AI API Model
boolean useVertexAi = Boolean.parseBoolean(System.getenv("GOOGLE_GENAI_USE_VERTEXAI"));
boolean useVertexAi =
Boolean.parseBoolean(AdkConfiguration.getOrDefault("GOOGLE_GENAI_USE_VERTEXAI", "false"));
if (useVertexAi && llmRequest.model().filter(ModelNameUtils::isGeminiModel).isPresent()) {
GenerateContentConfig config =
llmRequest.config().orElseGet(() -> GenerateContentConfig.builder().build());
Expand Down
88 changes: 88 additions & 0 deletions core/src/test/java/com/google/adk/config/AdkConfigurationTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.adk.config;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class AdkConfigurationTest {

private static final String KEY = "ADK_CONFIG_TEST_KEY";

@BeforeEach
void setUp() {
AdkConfiguration.clearAll();
System.clearProperty(KEY);
}

@AfterEach
void tearDown() {
AdkConfiguration.clearAll();
System.clearProperty(KEY);
}

@Test
void get_returnsEmpty_whenNoSourceProvidesValue() {
assertThat(AdkConfiguration.get(KEY)).isEmpty();
}

@Test
void get_returnsSystemProperty_whenSet() {
System.setProperty(KEY, "from-sysprop");
assertThat(AdkConfiguration.get(KEY)).hasValue("from-sysprop");
}

@Test
void set_takesPrecedenceOverSystemProperty() {
System.setProperty(KEY, "from-sysprop");
AdkConfiguration.set(KEY, "from-programmatic");
assertThat(AdkConfiguration.get(KEY)).hasValue("from-programmatic");
}

@Test
void clear_removesProgrammaticOverride_andFallsBackToSystemProperty() {
System.setProperty(KEY, "from-sysprop");
AdkConfiguration.set(KEY, "from-programmatic");
AdkConfiguration.clear(KEY);
assertThat(AdkConfiguration.get(KEY)).hasValue("from-sysprop");
}

@Test
void getOrDefault_returnsDefault_whenUnset() {
assertThat(AdkConfiguration.getOrDefault(KEY, "fallback")).isEqualTo("fallback");
}

@Test
void getOrDefault_returnsResolvedValue_whenSet() {
AdkConfiguration.set(KEY, "resolved");
assertThat(AdkConfiguration.getOrDefault(KEY, "fallback")).isEqualTo("resolved");
}

@Test
void set_throws_onNullKeyOrValue() {
assertThrows(IllegalArgumentException.class, () -> AdkConfiguration.set(null, "v"));
assertThrows(IllegalArgumentException.class, () -> AdkConfiguration.set(KEY, null));
}

@Test
void get_returnsEmpty_forNullKey() {
assertThat(AdkConfiguration.get(null)).isEmpty();
}
}