Skip to content

Commit 7dd1f6a

Browse files
matthew29tangcopybara-github
authored andcommitted
feat: Allow api key + proj/location for enterprise mode
PiperOrigin-RevId: 951031340
1 parent fadd4ce commit 7dd1f6a

4 files changed

Lines changed: 85 additions & 52 deletions

File tree

src/main/java/com/google/genai/ApiClient.java

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -163,18 +163,6 @@ protected ApiClient(
163163
customHttpOptions.flatMap(HttpOptions::baseUrl).map(url -> url.replaceAll("/$", ""));
164164

165165
// Validate constructor arguments combinations.
166-
if (hasProject && hasApiKey) {
167-
throw new IllegalArgumentException(
168-
"For Vertex AI APIs, project and API key are mutually exclusive in the client"
169-
+ " initializer. Please provide only one of them.");
170-
}
171-
172-
if (hasLocation && hasApiKey) {
173-
throw new IllegalArgumentException(
174-
"For Vertex AI APIs, location and API key are mutually exclusive in the client"
175-
+ " initializer. Please provide only one of them.");
176-
}
177-
178166
if (hasCredentials && hasApiKey) {
179167
throw new IllegalArgumentException(
180168
"For Vertex AI APIs, API key cannot be set together with credentials. Please provide"
@@ -189,20 +177,24 @@ protected ApiClient(
189177
+ " key from the environment variable.");
190178
apiKeyValue = null;
191179
}
192-
if (hasApiKey && (hasEnvProjectValue || hasEnvLocationValue)) {
180+
if (hasApiKey && !hasProject && !hasLocation && (hasEnvProjectValue || hasEnvLocationValue)) {
193181
// Explicit API key takes precedence over implicit project/location.
194182
logger.warning(
195183
"Warning: The user provided Vertex AI API key will take precedence over the"
196184
+ " project/location from the environment variables.");
197185
projectValue = null;
198186
locationValue = null;
199-
} else if ((hasProject || hasLocation) && hasEnvApiKeyValue) {
187+
} else if ((hasProject || hasLocation) && !hasApiKey && hasEnvApiKeyValue) {
200188
// Explicit project/location takes precedence over implicit API key.
201189
logger.warning(
202190
"Warning: The user provided project/location will take precedence over the API key from"
203191
+ " the environment variable.");
204192
apiKeyValue = null;
205-
} else if ((hasEnvProjectValue || hasEnvLocationValue) && hasEnvApiKeyValue) {
193+
} else if (!hasProject
194+
&& !hasLocation
195+
&& !hasApiKey
196+
&& (hasEnvProjectValue || hasEnvLocationValue)
197+
&& hasEnvApiKeyValue) {
206198
// Implicit project/location takes precedence over implicit API key.
207199
logger.warning(
208200
"Warning: The project/location from the environment variables will take precedence over"
@@ -233,7 +225,7 @@ protected ApiClient(
233225
initHttpOptionsBuilder.baseUrl(customBaseUrl.get());
234226
projectValue = null;
235227
locationValue = null;
236-
} else if (apiKeyValue != null
228+
} else if ((apiKeyValue != null && locationValue == null && !customBaseUrl.isPresent())
237229
|| (locationValue != null
238230
&& locationValue.equals("global")
239231
&& !customBaseUrl.isPresent())) {
@@ -253,9 +245,9 @@ protected ApiClient(
253245
this.location = Optional.ofNullable(locationValue);
254246
this.customBaseUrl = customBaseUrl;
255247

256-
// Only set credentials if using project/location.
248+
// Only set credentials if using project/location and no API key is provided.
257249
this.credentials =
258-
!this.project.isPresent()
250+
(!this.project.isPresent() || this.apiKey.isPresent())
259251
? Optional.empty()
260252
: Optional.of(credentials.orElseGet(() -> defaultCredentials()));
261253

@@ -794,10 +786,10 @@ boolean shouldPrependVertexProjectPath(
794786
&& httpOptions.baseUrlResourceScope().get().knownEnum() == Known.COLLECTION) {
795787
return false;
796788
}
797-
if (this.apiKey.isPresent()) {
789+
if (!this.vertexAI) {
798790
return false;
799791
}
800-
if (!this.vertexAI) {
792+
if (!this.project.isPresent() || !this.location.isPresent()) {
801793
return false;
802794
}
803795
if (path.startsWith("projects/")) {

src/main/java/com/google/genai/Client.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@ private Client(
227227

228228
if (enterprise.isPresent() && vertexAI.isPresent() && !enterprise.get().equals(vertexAI.get())) {
229229
throw new IllegalArgumentException(
230-
"enterprise and vertexAI flags have conflicting values, please set enterprise value only.");
230+
"enterprise and vertexAI flags have conflicting values, please set enterprise value"
231+
+ " only.");
231232
}
232233

233234
boolean useVertexAI;
@@ -245,7 +246,8 @@ private Client(
245246

246247
if (enterpriseEnvPresent && vertexEnvPresent && !enterpriseEnv.equalsIgnoreCase(vertexEnv)) {
247248
logger.warning(
248-
"Warning: Both GOOGLE_GENAI_USE_ENTERPRISE and GOOGLE_GENAI_USE_VERTEXAI are set with conflicting values. The value of GOOGLE_GENAI_USE_ENTERPRISE will be used.");
249+
"Warning: Both GOOGLE_GENAI_USE_ENTERPRISE and GOOGLE_GENAI_USE_VERTEXAI are set with"
250+
+ " conflicting values. The value of GOOGLE_GENAI_USE_ENTERPRISE will be used.");
249251
}
250252

251253
if (enterpriseEnvPresent) {
@@ -257,14 +259,8 @@ private Client(
257259
}
258260
}
259261

260-
if (project.isPresent() || location.isPresent()) {
261-
if (apiKey.isPresent()) {
262-
throw new IllegalArgumentException(
263-
"Project/location and API key are mutually exclusive in the client initializer.");
264-
}
265-
if (!useVertexAI) {
266-
throw new IllegalArgumentException("Gemini API do not support project/location.");
267-
}
262+
if ((project.isPresent() || location.isPresent()) && !useVertexAI) {
263+
throw new IllegalArgumentException("Gemini API does not support project/location.");
268264
}
269265

270266
this.debugConfig = debugConfig.orElse(new DebugConfig());

src/test/java/com/google/genai/ClientTest.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ public void testInitClientFromBuilder_setProjectInMldev_throwsException() {
150150
() -> Client.builder().vertexAI(false).project(PROJECT).build());
151151

152152
// Assert
153-
assertEquals("Gemini API do not support project/location.", exception.getMessage());
153+
assertEquals("Gemini API does not support project/location.", exception.getMessage());
154154
}
155155

156156
@Test
@@ -162,9 +162,21 @@ public void testInitClientFromBuilder_setBothApiKeyAndProject_throwsException()
162162
() -> Client.builder().apiKey(API_KEY).project(PROJECT).build());
163163

164164
// Assert
165-
assertEquals(
166-
"Project/location and API key are mutually exclusive in the client initializer.",
167-
exception.getMessage());
165+
assertEquals("Gemini API does not support project/location.", exception.getMessage());
166+
}
167+
168+
@Test
169+
public void testInitClientFromBuilder_setApiKeyAndProjectAndLocationInVertex_success() {
170+
// Act
171+
Client client =
172+
Client.builder().apiKey(API_KEY).project(PROJECT).location(LOCATION).vertexAI(true).build();
173+
174+
// Assert
175+
assertEquals(API_KEY, client.apiKey());
176+
assertEquals(PROJECT, client.project());
177+
assertEquals(LOCATION, client.location());
178+
assertTrue(client.vertexAI());
179+
assertEquals("https://location-aiplatform.googleapis.com", client.baseUrl().orElse(null));
168180
}
169181

170182
@Test

src/test/java/com/google/genai/HttpApiClientTest.java

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,44 @@ public void testRequestPostMethodWithVertexAI() throws Exception {
145145
assertEquals("Bearer", capturedRequest.header("Authorization"));
146146
assertNull(capturedRequest.header("x-goog-api-key"));
147147

148+
RequestBody body = capturedRequest.body();
149+
assertNotNull(body);
150+
final Buffer buffer = new Buffer();
151+
body.writeTo(buffer);
152+
assertEquals("application/json; charset=utf-8", body.contentType().toString());
153+
}
154+
155+
@Test
156+
public void testRequestPostMethodWithVertexExpressMode() throws Exception {
157+
// Arrange
158+
HttpApiClient client =
159+
new HttpApiClient(
160+
Optional.of(API_KEY),
161+
Optional.of(PROJECT),
162+
Optional.of(LOCATION),
163+
Optional.empty(),
164+
Optional.empty(),
165+
Optional.empty());
166+
setMockClient(client);
167+
168+
// Act
169+
client.request("POST", TEST_PATH, TEST_REQUEST_JSON, Optional.empty());
170+
171+
// Assert
172+
ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
173+
verify(mockHttpClient).newCall(requestCaptor.capture());
174+
Request capturedRequest = requestCaptor.getValue();
175+
176+
assertEquals("POST", capturedRequest.method());
177+
assertEquals(
178+
String.format(
179+
"https://%s-aiplatform.googleapis.com/v1beta1/projects/%s/locations/%s/%s",
180+
LOCATION, PROJECT, LOCATION, TEST_PATH),
181+
capturedRequest.url().toString());
182+
assertNotNull(capturedRequest.header("x-goog-api-key"));
183+
assertEquals(API_KEY, capturedRequest.header("x-goog-api-key"));
184+
assertNull(capturedRequest.header("Authorization"));
185+
148186
RequestBody body = capturedRequest.body();
149187
assertNotNull(body);
150188
final Buffer buffer = new Buffer();
@@ -797,24 +835,19 @@ public void testInitHttpClientWithCustomCredentialsAndApiKey_throwsException() t
797835
}
798836

799837
@Test
800-
public void testInitHttpClientVertexWithProjectLocationAndApiKey_throwsException()
801-
throws Exception {
802-
// Explicit proj/location and API key are not allowed.
803-
IllegalArgumentException exception =
804-
assertThrows(
805-
IllegalArgumentException.class,
806-
() ->
807-
new HttpApiClient(
808-
Optional.of(API_KEY),
809-
Optional.of(PROJECT),
810-
Optional.of(LOCATION),
811-
Optional.empty(),
812-
Optional.empty(),
813-
Optional.empty()));
814-
assertEquals(
815-
"For Vertex AI APIs, project and API key are mutually exclusive in the client initializer."
816-
+ " Please provide only one of them.",
817-
exception.getMessage());
838+
public void testInitHttpClientVertexWithProjectLocationAndApiKey_success() throws Exception {
839+
HttpApiClient client =
840+
new HttpApiClient(
841+
Optional.of(API_KEY),
842+
Optional.of(PROJECT),
843+
Optional.of(LOCATION),
844+
Optional.empty(),
845+
Optional.empty(),
846+
Optional.empty());
847+
assertEquals(API_KEY, client.apiKey());
848+
assertEquals(PROJECT, client.project());
849+
assertEquals(LOCATION, client.location());
850+
assertTrue(client.vertexAI());
818851
}
819852

820853
@Test

0 commit comments

Comments
 (0)