Skip to content

Commit 8263000

Browse files
committed
Send API key via x-goog-api-key header, not URL query
Refactor all Gemini API functions to send the API key using the x-goog-api-key HTTP header instead of as a URL query parameter. Update documentation and Rd files to reflect this change. Remove all uses of req_url_query for API key transmission and update httr2 import statements accordingly.
1 parent d603ed9 commit 8263000

File tree

13 files changed

+160
-116
lines changed

13 files changed

+160
-116
lines changed

NAMESPACE

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ importFrom(httr2,req_headers)
4242
importFrom(httr2,req_method)
4343
importFrom(httr2,req_perform)
4444
importFrom(httr2,req_timeout)
45-
importFrom(httr2,req_url_query)
4645
importFrom(httr2,request)
4746
importFrom(httr2,resp_body_json)
4847
importFrom(httr2,resp_body_string)

R/gemini.R

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
#' setAPI("YOUR_API_KEY")
2323
#' gemini("Explain dplyr's mutate function")
2424
#' }
25-
#' @importFrom httr2 request req_url_query req_headers req_body_json req_perform resp_body_json req_timeout
25+
#' @importFrom httr2 request req_headers req_body_json req_perform resp_body_json req_timeout
2626
#' @importFrom cli cli_status_clear cli_status
2727
#'
2828
#' @seealso https://ai.google.dev/docs/gemini_api_overview#text_input
@@ -53,7 +53,7 @@ gemini <- function(prompt, model = "2.0-flash", temperature = 1, maxOutputTokens
5353
if (model == "2.0-flash-exp-image-generation") {
5454
generation_config$responseModalities <- list("Text", "Image")
5555
}
56-
56+
5757
# Create request body as a separate list
5858
request_body <- list(
5959
contents = list(
@@ -66,12 +66,14 @@ gemini <- function(prompt, model = "2.0-flash", temperature = 1, maxOutputTokens
6666

6767
# Set timeout using req_timeout
6868
req <- request(url) |>
69-
req_url_query(key = api_key) |>
70-
req_headers("Content-Type" = "application/json") |>
69+
req_headers(
70+
"x-goog-api-key" = api_key,
71+
"Content-Type" = "application/json"
72+
) |>
7173
req_body_json(request_body) |>
7274
req_timeout(as.integer(timeout))
7375
resp <- req_perform(req)
74-
76+
7577
# Add logic to check status code
7678
if (resp$status_code != 200) {
7779
cli_status_clear(id = sb)
@@ -133,7 +135,7 @@ gemini.vertex <- function(prompt = NULL, tokens = NULL, temperature = 1, maxOutp
133135
topK = topK,
134136
seed = seed
135137
)
136-
138+
137139
# Create request body as a separate list
138140
request_body <- list(
139141
contents = list(
@@ -155,22 +157,22 @@ gemini.vertex <- function(prompt = NULL, tokens = NULL, temperature = 1, maxOutp
155157
) |>
156158
req_body_json(request_body) |>
157159
req_timeout(as.integer(timeout))
158-
160+
159161
resp <- req_perform(req)
160-
162+
161163
# Add logic to check status code
162164
if (resp$status_code != 200) {
163165
cli_status_clear(id = sb)
164166
cli_alert_danger(paste0("Error in generate request: Status code ", resp$status_code))
165167
return(NULL)
166168
}
167-
169+
168170
response <- resp_body_json(resp)
169171

170172
cli_status_clear(id = sb)
171-
173+
172174
candidates <- response$candidates
173175
outputs <- unlist(lapply(candidates, function(candidate) candidate$content$parts))
174-
176+
175177
return(outputs)
176178
}

R/gemini_audio.R

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
#' @param seed The seed to use. Default is 1234 value should be integer
1717
#' see https://ai.google.dev/gemini-api/docs/models/generative-models#model-parameters
1818
#'
19+
#' @details
20+
#' The API key is now sent via the HTTP header \code{x-goog-api-key} instead of as a URL query parameter.
21+
#'
1922
#' @return A character vector containing the Gemini API's response.
2023
#'
2124
#' @export
@@ -29,7 +32,7 @@
2932
#'
3033
#' @importFrom tools file_ext
3134
#' @importFrom cli cli_alert_danger cli_status cli_status_clear cli_alert_warning cli_alert_info
32-
#' @importFrom httr2 request req_url_query req_headers req_body_json req_perform resp_body_json req_method req_body_file resp_header
35+
#' @importFrom httr2 request req_headers req_body_json req_perform resp_body_json req_method req_body_file resp_header
3336
#'
3437
#'
3538
gemini_audio <- function(audio = NULL, prompt = "Describe this audio", model = "2.0-flash",
@@ -51,16 +54,18 @@ gemini_audio <- function(audio = NULL, prompt = "Describe this audio", model = "
5154

5255
ext <- tolower(tools::file_ext(audio))
5356

54-
if(ext == ""){
57+
if (ext == "") {
5558
cli_alert_warning("File extension not found. Please check the file path.")
5659
return(NULL)
5760
}
5861

5962
# 2. Error handling for unsupported file extensions
6063
supported_extensions <- c("mp3", "wav", "aiff", "aac", "ogg", "flac")
6164
if (!(ext %in% supported_extensions)) {
62-
cli_alert_danger(paste0("Unsupported file extension: '", ext, "'. Currently supported extensions are: ",
63-
paste(supported_extensions, collapse=", ")))
65+
cli_alert_danger(paste0(
66+
"Unsupported file extension: '", ext, "'. Currently supported extensions are: ",
67+
paste(supported_extensions, collapse = ", ")
68+
))
6469
cli_alert_info("Please submit an issue at https://github.com/jhk0530/gemini.R/issues for additional file format support.")
6570
return(NULL)
6671
}
@@ -70,18 +75,18 @@ gemini_audio <- function(audio = NULL, prompt = "Describe this audio", model = "
7075

7176
# Special case override: not defined yet
7277
# special_cases <- list(mp3 = "audio/mpeg")
73-
#if (!is.null(special_cases[[ext]])) {
78+
# if (!is.null(special_cases[[ext]])) {
7479
# mime_type <- special_cases[[ext]]
75-
#}
76-
80+
# }
81+
7782
num_bytes <- file.info(audio)$size
7883

7984
# Prepare file upload
8085
resumable_request <-
8186
request(file_url) |>
82-
req_url_query(key = api_key) |>
8387
req_method("POST") |>
8488
req_headers(
89+
"x-goog-api-key" = api_key,
8590
"X-Goog-Upload-Protocol" = "resumable",
8691
"X-Goog-Upload-Command" = "start",
8792
"X-Goog-Upload-Header-Content-Length" = as.character(num_bytes),
@@ -129,7 +134,7 @@ gemini_audio <- function(audio = NULL, prompt = "Describe this audio", model = "
129134

130135
# Add this line to define model_query
131136
model_query <- paste0("gemini-", model, ":generateContent")
132-
137+
133138
url <- paste0("https://generativelanguage.googleapis.com/v1beta/models/", model_query)
134139

135140
sb <- cli_status("Gemini is analyzing audio...")
@@ -142,7 +147,7 @@ gemini_audio <- function(audio = NULL, prompt = "Describe this audio", model = "
142147
topK = topK,
143148
seed = seed
144149
)
145-
150+
146151
# Compose request body as a separate list
147152
request_body <- list(
148153
contents = list(
@@ -155,9 +160,11 @@ gemini_audio <- function(audio = NULL, prompt = "Describe this audio", model = "
155160
)
156161

157162
generate_req <- request(url) |>
158-
req_url_query(key = api_key) |>
159163
req_method("POST") |>
160-
req_headers("Content-Type" = "application/json") |>
164+
req_headers(
165+
"x-goog-api-key" = api_key,
166+
"Content-Type" = "application/json"
167+
) |>
161168
req_body_json(request_body)
162169

163170
generate_resp <- req_perform(generate_req)
@@ -224,7 +231,7 @@ gemini_audio.vertex <- function(audio = NULL, prompt = "Describe this audio", to
224231
topK = topK,
225232
seed = seed
226233
)
227-
234+
228235
# Compose request body as a separate list
229236
request_body <- list(
230237
contents = list(

R/gemini_chat.R

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
#' chats <- gemini_chat("How do you think about summer?", chats$history)
3232
#' print(chats$outputs)
3333
#' }
34-
#' @importFrom httr2 request req_url_query req_headers req_body_json req_perform resp_body_json
34+
#' @importFrom httr2 request req_headers req_body_json req_perform resp_body_json
3535
#' @importFrom cli cli_alert_danger cli_status_clear cli_status
3636
#' @seealso https://ai.google.dev/docs/gemini_api_overview#chat
3737
#'
@@ -51,7 +51,7 @@ gemini_chat <- function(prompt, history = list(), model = "2.0-flash", temperatu
5151
api_key <- Sys.getenv("GEMINI_API_KEY")
5252

5353
sb <- cli_status("Gemini is answering...")
54-
54+
5555
# 2. Create generation_config as a separate list
5656
generation_config <- list(
5757
temperature = temperature,
@@ -60,32 +60,34 @@ gemini_chat <- function(prompt, history = list(), model = "2.0-flash", temperatu
6060
topK = topK,
6161
seed = seed
6262
)
63-
63+
6464
# Create request body as a separate variable
6565
request_body <- list(
6666
contents = history,
6767
generationConfig = generation_config
6868
)
69-
69+
7070
req <- request(url) |>
71-
req_url_query(key = api_key) |>
72-
req_headers("Content-Type" = "application/json") |>
71+
req_headers(
72+
"x-goog-api-key" = api_key,
73+
"Content-Type" = "application/json"
74+
) |>
7375
req_body_json(request_body)
7476

7577
resp <- req_perform(req)
76-
78+
7779
# 3. Add status code validation
7880
if (resp$status_code != 200) {
7981
cli_status_clear(id = sb)
8082
cli_alert_danger(paste0("Error in generate request: Status code ", resp$status_code))
8183
return(NULL)
8284
}
83-
85+
8486
cli_status_clear(id = sb)
8587

8688
candidates <- resp_body_json(resp)$candidates
8789
outputs <- unlist(lapply(candidates, function(candidate) candidate$content$parts))
88-
90+
8991
# 4. Improve response handling - handle multiple responses
9092
if (length(outputs) > 0) {
9193
# Record the first response
@@ -116,14 +118,14 @@ addHistory <- function(history, role = NULL, item = NULL) {
116118
cli_alert_danger("{.arg item} must not be NULL")
117119
return(NULL)
118120
}
119-
121+
120122
# Add role validation
121123
valid_roles <- c("user", "model")
122124
if (!(role %in% valid_roles)) {
123-
cli_alert_danger(paste0("Invalid role: '", role, "'. Must be one of: ", paste(valid_roles, collapse=", ")))
125+
cli_alert_danger(paste0("Invalid role: '", role, "'. Must be one of: ", paste(valid_roles, collapse = ", ")))
124126
return(NULL)
125127
}
126-
128+
127129
history[[length(history) + 1]] <-
128130
list(
129131
role = role,

R/gemini_docs.R

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#' @param pdf_path Path(s) to the local file(s). Can be a character vector.
66
#' @param prompt The prompt to send to Gemini (e.g., "Summarize these documents").
77
#' @param type File type. One of "PDF", "JavaScript", "Python", "TXT", "HTML", "CSS", "Markdown", "CSV", "XML", "RTF". Default is "PDF".
8-
#' @param api_key Gemini API key. Defaults to \code{Sys.getenv("GEMINI_API_KEY")}.
8+
#' @param api_key Gemini API key. Defaults to \code{Sys.getenv("GEMINI_API_KEY")}. The API key is sent via the HTTP header \code{x-goog-api-key}.
99
#'
1010
#' @return The summary or response text from Gemini.
1111
#'
@@ -21,21 +21,21 @@
2121
#' )
2222
#' }
2323
#'
24-
#' @importFrom httr2 request req_url_query req_headers req_body_json req_perform resp_body_json
24+
#' @importFrom httr2 request req_headers req_body_json req_perform resp_body_json
2525
#' @importFrom base64enc base64encode
2626
#'
2727
#' @export
2828
#' @seealso https://ai.google.dev/gemini-api/docs/document-processing?lang=rest
2929
gemini_docs <- function(pdf_path, prompt, type = "PDF", api_key = Sys.getenv("GEMINI_API_KEY"), large = FALSE, local = FALSE) {
3030
# If local = FALSE and input is a URL, download to a temp file
3131
temp_files <- character(0)
32-
if (!local) {
32+
if (!local) {
3333
# Download the file to a temp file
3434
temp_file <- tempfile(fileext = paste0(".", tools::file_ext(pdf_path)))
3535
download.file(pdf_path, temp_file, mode = "wb", quiet = TRUE)
3636
temp_files <- c(temp_files, temp_file)
3737
pdf_path <- temp_file
38-
38+
3939
# Remove temp files on exit
4040
if (length(temp_files) > 0) on.exit(unlink(temp_files), add = TRUE)
4141
}
@@ -88,8 +88,10 @@ gemini_docs <- function(pdf_path, prompt, type = "PDF", api_key = Sys.getenv("GE
8888
url <- "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
8989

9090
req <- httr2::request(url) |>
91-
httr2::req_url_query(key = api_key) |>
92-
httr2::req_headers("Content-Type" = "application/json") |>
91+
httr2::req_headers(
92+
"x-goog-api-key" = api_key,
93+
"Content-Type" = "application/json"
94+
) |>
9395
httr2::req_body_json(body, auto_unbox = TRUE)
9496

9597
resp <- httr2::req_perform(req)
@@ -130,8 +132,10 @@ gemini_docs <- function(pdf_path, prompt, type = "PDF", api_key = Sys.getenv("GE
130132
url <- "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"
131133

132134
req <- httr2::request(url) |>
133-
httr2::req_url_query(key = api_key) |>
134-
httr2::req_headers("Content-Type" = "application/json") |>
135+
httr2::req_headers(
136+
"x-goog-api-key" = api_key,
137+
"Content-Type" = "application/json"
138+
) |>
135139
httr2::req_body_json(body, auto_unbox = TRUE)
136140

137141
resp <- httr2::req_perform(req)
@@ -183,7 +187,7 @@ gemini_docs <- function(pdf_path, prompt, type = "PDF", api_key = Sys.getenv("GE
183187
#' @seealso https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/send-request-document
184188

185189
gemini_docs.vertex <- function(file_uri, prompt, mime_type = "application/pdf", tokens = NULL,
186-
temperature = 1, maxOutputTokens = 8192, topK = 40, topP = 0.95, seed = 1234) {
190+
temperature = 1, maxOutputTokens = 8192, topK = 40, topP = 0.95, seed = 1234) {
187191
# Validate input parameters
188192
if (missing(file_uri) || length(file_uri) < 1) {
189193
stop("At least one file_uri must be provided.")
@@ -253,8 +257,8 @@ upload_api <- function(local_file, api_key, mime_type) {
253257
file_size <- file.info(local_file)$size
254258
meta_body <- list(file = list(display_name = basename(local_file)))
255259
meta_req <- httr2::request("https://generativelanguage.googleapis.com/upload/v1beta/files") |>
256-
httr2::req_url_query(key = api_key) |>
257260
httr2::req_headers(
261+
"x-goog-api-key" = api_key,
258262
"X-Goog-Upload-Protocol" = "resumable",
259263
"X-Goog-Upload-Command" = "start",
260264
"X-Goog-Upload-Header-Content-Length" = as.character(file_size),
@@ -287,4 +291,4 @@ upload_api <- function(local_file, api_key, mime_type) {
287291
file_uri <- upload_result$file$uri
288292
if (is.null(file_uri)) stop("Failed to get file_uri after upload.")
289293
return(file_uri)
290-
}
294+
}

0 commit comments

Comments
 (0)