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 =
@@ -75,61 +71,53 @@ private static OkHttpClient prepareHttpClient(@Nullable ExecutorService executor
7571 .build ();
7672 }
7773
78- private static Client buildApiKeyClient (
79- String apiKey , @ Nullable ExecutorService executorService ) {
74+ // Every client ADK builds starts here, so each sends ADK's tracking tag.
75+ private static Client . Builder adkClientBuilder ( @ Nullable ExecutorService executorService ) {
8076 return Client .builder ()
81- .apiKey (apiKey )
82- .httpOptions (HttpOptions .builder ().headers (TRACKING_HEADERS ).build ())
77+ .httpOptions (HttpOptions .builder ().headers (TrackingHeaders .GENAI_SDK_HEADERS ).build ())
8378 .clientOptions (
84- ClientOptions .builder ().customHttpClient (prepareHttpClient (executorService )).build ())
85- .build ();
79+ ClientOptions .builder ().customHttpClient (prepareHttpClient (executorService )).build ());
80+ }
81+
82+ private static Client buildApiKeyClient (
83+ String apiKey , @ Nullable ExecutorService executorService ) {
84+ return adkClientBuilder (executorService ).apiKey (apiKey ).build ();
8685 }
8786
8887 private static Client buildVertexClient (
8988 VertexCredentials vertexCredentials , @ Nullable ExecutorService executorService ) {
90- Client .Builder apiClientBuilder =
91- Client .builder ()
92- .httpOptions (HttpOptions .builder ().headers (TRACKING_HEADERS ).build ())
93- .clientOptions (
94- ClientOptions .builder ()
95- .customHttpClient (prepareHttpClient (executorService ))
96- .build ());
89+ Client .Builder apiClientBuilder = adkClientBuilder (executorService );
9790 vertexCredentials .project ().ifPresent (apiClientBuilder ::project );
9891 vertexCredentials .location ().ifPresent (apiClientBuilder ::location );
9992 vertexCredentials .credentials ().ifPresent (apiClientBuilder ::credentials );
10093 return apiClientBuilder .build ();
10194 }
10295
10396 private static Client buildDefaultClient (@ Nullable ExecutorService executorService ) {
104- return Client .builder ()
105- .httpOptions (HttpOptions .builder ().headers (TRACKING_HEADERS ).build ())
106- .clientOptions (
107- ClientOptions .builder ().customHttpClient (prepareHttpClient (executorService )).build ())
108- .build ();
109- }
110-
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 );
97+ return adkClientBuilder (executorService ).build ();
12098 }
12199
122100 private final Client apiClient ;
101+ // True when apiClient already sends ADK's tracking tag.
102+ private final boolean clientSendsTrackingHeaders ;
123103
124104 /**
125- * Constructs a new Gemini instance.
105+ * Constructs a new Gemini instance. Each generate request sent through {@code apiClient} carries
106+ * ADK's tracking tag, which the genai SDK appends to the client's tracking headers. If the
107+ * client's own headers already carry an ADK tag, requests send two because the SDK does not
108+ * expose a client's headers.
126109 *
127110 * @param modelName The name of the Gemini model to use (e.g., "gemini-2.0-flash").
128111 * @param apiClient The genai {@link com.google.genai.Client} instance for making API calls.
129112 */
130113 public Gemini (String modelName , Client apiClient ) {
114+ this (modelName , apiClient , /* clientSendsTrackingHeaders= */ false );
115+ }
116+
117+ Gemini (String modelName , Client apiClient , boolean clientSendsTrackingHeaders ) {
131118 super (modelName );
132119 this .apiClient = Objects .requireNonNull (apiClient , "apiClient cannot be null" );
120+ this .clientSendsTrackingHeaders = clientSendsTrackingHeaders ;
133121 }
134122
135123 /**
@@ -139,9 +127,10 @@ public Gemini(String modelName, Client apiClient) {
139127 * @param apiKey The Google Gemini API key.
140128 */
141129 public Gemini (String modelName , String apiKey ) {
142- super (modelName );
143- Objects .requireNonNull (apiKey , "apiKey cannot be null" );
144- this .apiClient = buildApiKeyClient (apiKey , null );
130+ this (
131+ modelName ,
132+ buildApiKeyClient (Objects .requireNonNull (apiKey , "apiKey cannot be null" ), null ),
133+ /* clientSendsTrackingHeaders= */ true );
145134 }
146135
147136 /**
@@ -151,9 +140,11 @@ public Gemini(String modelName, String apiKey) {
151140 * @param vertexCredentials The Vertex AI credentials to access the Gemini model.
152141 */
153142 public Gemini (String modelName , VertexCredentials vertexCredentials ) {
154- super (modelName );
155- Objects .requireNonNull (vertexCredentials , "vertexCredentials cannot be null" );
156- this .apiClient = buildVertexClient (vertexCredentials , null );
143+ this (
144+ modelName ,
145+ buildVertexClient (
146+ Objects .requireNonNull (vertexCredentials , "vertexCredentials cannot be null" ), null ),
147+ /* clientSendsTrackingHeaders= */ true );
157148 }
158149
159150 /**
@@ -256,13 +247,16 @@ public Gemini build() {
256247
257248 if (apiClient != null ) {
258249 return new Gemini (modelName , apiClient );
259- } else if (apiKey != null ) {
260- return new Gemini (modelName , buildApiKeyClient (apiKey , httpExecutorService ));
250+ }
251+ Client client ;
252+ if (apiKey != null ) {
253+ client = buildApiKeyClient (apiKey , httpExecutorService );
261254 } else if (vertexCredentials != null ) {
262- return new Gemini ( modelName , buildVertexClient (vertexCredentials , httpExecutorService ) );
255+ client = buildVertexClient (vertexCredentials , httpExecutorService );
263256 } else {
264- return new Gemini ( modelName , buildDefaultClient (httpExecutorService ) );
257+ client = buildDefaultClient (httpExecutorService );
265258 }
259+ return new Gemini (modelName , client , /* clientSendsTrackingHeaders= */ true );
266260 }
267261 }
268262
@@ -292,6 +286,9 @@ private Flowable<LlmResponse> sendRequest(
292286 logger .trace ("Request Contents: {}" , llmRequest .contents ());
293287 logger .trace ("Request Config: {}" , config );
294288
289+ if (!clientSendsTrackingHeaders ) {
290+ config = withTrackingHeaders (config );
291+ }
295292 if (stream ) {
296293 logger .debug ("Sending streaming generateContent request to model {}" , effectiveModelName );
297294 CompletableFuture <ResponseStream <GenerateContentResponse >> streamFuture =
@@ -316,6 +313,23 @@ private Flowable<LlmResponse> sendRequest(
316313 }
317314 }
318315
316+ /**
317+ * Returns {@code config} with ADK's tracking tag merged into its request headers, which the genai
318+ * SDK appends to the client's own.
319+ */
320+ private static GenerateContentConfig withTrackingHeaders (@ Nullable GenerateContentConfig config ) {
321+ GenerateContentConfig base = config != null ? config : GenerateContentConfig .builder ().build ();
322+ HttpOptions httpOptions = base .httpOptions ().orElseGet (() -> HttpOptions .builder ().build ());
323+ return base .toBuilder ()
324+ .httpOptions (
325+ httpOptions .toBuilder ()
326+ .headers (
327+ TrackingHeaders .mergeIntoGenAiSdkHeaders (
328+ httpOptions .headers ().orElse (ImmutableMap .of ())))
329+ .build ())
330+ .build ();
331+ }
332+
319333 private static LlmResponse withCacheMetadata (
320334 LlmResponse response , @ Nullable CacheMetadata cacheMetadata ) {
321335 return cacheMetadata == null
0 commit comments