1616
1717package com .google .adk .models ;
1818
19- import static com .google .common .base .StandardSystemProperty .JAVA_VERSION ;
20-
21- import com .google .adk .Version ;
2219import com .google .adk .internal .http .HttpClientFactory ;
2320import com .google .common .annotations .VisibleForTesting ;
2421import com .google .common .collect .ImmutableList ;
6057public class Gemini extends BaseLlm {
6158
6259 private static final Logger logger = LoggerFactory .getLogger (Gemini .class );
63- private static final ImmutableMap <String , String > TRACKING_HEADERS ;
6460
6561 private static OkHttpClient prepareHttpClient (@ Nullable ExecutorService executorService ) {
6662 OkHttpClient client =
@@ -79,7 +75,7 @@ private static Client buildApiKeyClient(
7975 String apiKey , @ Nullable ExecutorService executorService ) {
8076 return Client .builder ()
8177 .apiKey (apiKey )
82- .httpOptions (HttpOptions .builder ().headers (TRACKING_HEADERS ).build ())
78+ .httpOptions (HttpOptions .builder ().headers (TrackingHeaders . HEADERS ).build ())
8379 .clientOptions (
8480 ClientOptions .builder ().customHttpClient (prepareHttpClient (executorService )).build ())
8581 .build ();
@@ -89,7 +85,7 @@ private static Client buildVertexClient(
8985 VertexCredentials vertexCredentials , @ Nullable ExecutorService executorService ) {
9086 Client .Builder apiClientBuilder =
9187 Client .builder ()
92- .httpOptions (HttpOptions .builder ().headers (TRACKING_HEADERS ).build ())
88+ .httpOptions (HttpOptions .builder ().headers (TrackingHeaders . HEADERS ).build ())
9389 .clientOptions (
9490 ClientOptions .builder ()
9591 .customHttpClient (prepareHttpClient (executorService ))
@@ -102,34 +98,31 @@ private static Client buildVertexClient(
10298
10399 private static Client buildDefaultClient (@ Nullable ExecutorService executorService ) {
104100 return Client .builder ()
105- .httpOptions (HttpOptions .builder ().headers (TRACKING_HEADERS ).build ())
101+ .httpOptions (HttpOptions .builder ().headers (TrackingHeaders . HEADERS ).build ())
106102 .clientOptions (
107103 ClientOptions .builder ().customHttpClient (prepareHttpClient (executorService )).build ())
108104 .build ();
109105 }
110106
111- static {
112- String frameworkLabel = "google-adk/" + Version .JAVA_ADK_VERSION ;
113- String languageLabel = "gl-java/" + JAVA_VERSION .value ();
114- String versionHeaderValue = String .format ("%s %s" , frameworkLabel , languageLabel );
115-
116- TRACKING_HEADERS =
117- ImmutableMap .of (
118- "x-goog-api-client" , versionHeaderValue ,
119- "user-agent" , versionHeaderValue );
120- }
121-
122107 private final Client apiClient ;
108+ // True when apiClient already sends TrackingHeaders.HEADERS.
109+ private final boolean clientSendsTrackingHeaders ;
123110
124111 /**
125- * Constructs a new Gemini instance.
112+ * Constructs a new Gemini instance. Each generate request sent through {@code apiClient} carries
113+ * ADK's tracking headers.
126114 *
127115 * @param modelName The name of the Gemini model to use (e.g., "gemini-2.0-flash").
128116 * @param apiClient The genai {@link com.google.genai.Client} instance for making API calls.
129117 */
130118 public Gemini (String modelName , Client apiClient ) {
119+ this (modelName , apiClient , /* clientSendsTrackingHeaders= */ false );
120+ }
121+
122+ Gemini (String modelName , Client apiClient , boolean clientSendsTrackingHeaders ) {
131123 super (modelName );
132124 this .apiClient = Objects .requireNonNull (apiClient , "apiClient cannot be null" );
125+ this .clientSendsTrackingHeaders = clientSendsTrackingHeaders ;
133126 }
134127
135128 /**
@@ -139,9 +132,10 @@ public Gemini(String modelName, Client apiClient) {
139132 * @param apiKey The Google Gemini API key.
140133 */
141134 public Gemini (String modelName , String apiKey ) {
142- super (modelName );
143- Objects .requireNonNull (apiKey , "apiKey cannot be null" );
144- this .apiClient = buildApiKeyClient (apiKey , null );
135+ this (
136+ modelName ,
137+ buildApiKeyClient (Objects .requireNonNull (apiKey , "apiKey cannot be null" ), null ),
138+ /* clientSendsTrackingHeaders= */ true );
145139 }
146140
147141 /**
@@ -151,9 +145,11 @@ public Gemini(String modelName, String apiKey) {
151145 * @param vertexCredentials The Vertex AI credentials to access the Gemini model.
152146 */
153147 public Gemini (String modelName , VertexCredentials vertexCredentials ) {
154- super (modelName );
155- Objects .requireNonNull (vertexCredentials , "vertexCredentials cannot be null" );
156- this .apiClient = buildVertexClient (vertexCredentials , null );
148+ this (
149+ modelName ,
150+ buildVertexClient (
151+ Objects .requireNonNull (vertexCredentials , "vertexCredentials cannot be null" ), null ),
152+ /* clientSendsTrackingHeaders= */ true );
157153 }
158154
159155 /**
@@ -256,13 +252,16 @@ public Gemini build() {
256252
257253 if (apiClient != null ) {
258254 return new Gemini (modelName , apiClient );
259- } else if (apiKey != null ) {
260- return new Gemini (modelName , buildApiKeyClient (apiKey , httpExecutorService ));
255+ }
256+ Client client ;
257+ if (apiKey != null ) {
258+ client = buildApiKeyClient (apiKey , httpExecutorService );
261259 } else if (vertexCredentials != null ) {
262- return new Gemini ( modelName , buildVertexClient (vertexCredentials , httpExecutorService ) );
260+ client = buildVertexClient (vertexCredentials , httpExecutorService );
263261 } else {
264- return new Gemini ( modelName , buildDefaultClient (httpExecutorService ) );
262+ client = buildDefaultClient (httpExecutorService );
265263 }
264+ return new Gemini (modelName , client , /* clientSendsTrackingHeaders= */ true );
266265 }
267266 }
268267
@@ -292,6 +291,9 @@ private Flowable<LlmResponse> sendRequest(
292291 logger .trace ("Request Contents: {}" , llmRequest .contents ());
293292 logger .trace ("Request Config: {}" , config );
294293
294+ if (!clientSendsTrackingHeaders ) {
295+ config = withTrackingHeaders (config );
296+ }
295297 if (stream ) {
296298 logger .debug ("Sending streaming generateContent request to model {}" , effectiveModelName );
297299 CompletableFuture <ResponseStream <GenerateContentResponse >> streamFuture =
@@ -316,6 +318,21 @@ private Flowable<LlmResponse> sendRequest(
316318 }
317319 }
318320
321+ /**
322+ * Returns {@code config} with ADK's tracking headers merged into its request headers, which the
323+ * genai SDK appends to the client's own.
324+ */
325+ private static GenerateContentConfig withTrackingHeaders (@ Nullable GenerateContentConfig config ) {
326+ GenerateContentConfig base = config != null ? config : GenerateContentConfig .builder ().build ();
327+ HttpOptions httpOptions = base .httpOptions ().orElseGet (() -> HttpOptions .builder ().build ());
328+ return base .toBuilder ()
329+ .httpOptions (
330+ httpOptions .toBuilder ()
331+ .headers (TrackingHeaders .mergeInto (httpOptions .headers ().orElse (ImmutableMap .of ())))
332+ .build ())
333+ .build ();
334+ }
335+
319336 private static LlmResponse withCacheMetadata (
320337 LlmResponse response , @ Nullable CacheMetadata cacheMetadata ) {
321338 return cacheMetadata == null
0 commit comments