2626# '
2727# ' @export
2828# ' @seealso https://ai.google.dev/gemini-api/docs/document-processing?lang=rest
29- gemini_docs <- function (pdf_path , prompt , type = " PDF" , api_key = Sys.getenv(" GEMINI_API_KEY" )) {
29+ gemini_docs <- function (pdf_path , prompt , type = " PDF" , api_key = Sys.getenv(" GEMINI_API_KEY" ), large = FALSE , local = FALSE ) {
30+ # If local = FALSE and input is a URL, download to a temp file
31+ temp_files <- character (0 )
32+ if (! local ) {
33+ # Download the file to a temp file
34+ temp_file <- tempfile(fileext = paste0(" ." , tools :: file_ext(pdf_path )))
35+ download.file(pdf_path , temp_file , mode = " wb" , quiet = TRUE )
36+ temp_files <- c(temp_files , temp_file )
37+ pdf_path <- temp_file
38+
39+ # Remove temp files on exit
40+ if (length(temp_files ) > 0 ) on.exit(unlink(temp_files ), add = TRUE )
41+ }
42+
43+ # Check file existence
3044 if (length(pdf_path ) < 1 ) stop(" At least one file path must be provided." )
3145 if (any(! file.exists(pdf_path ))) stop(" Some files do not exist: " , paste(pdf_path [! file.exists(pdf_path )], collapse = " , " ))
3246
@@ -52,53 +66,92 @@ gemini_docs <- function(pdf_path, prompt, type = "PDF", api_key = Sys.getenv("GE
5266 # Use the first mime type if multiple are available
5367 mime_type <- if (is.character(mime_types [[type ]])) mime_types [[type ]][1 ] else as.character(mime_types [[type ]][1 ])
5468
55- # Base64 encode all files
56- file_parts <- lapply(pdf_path , function (path ) {
57- list (
58- inline_data = list (
59- mime_type = mime_type ,
60- data = base64enc :: base64encode(path )
69+ if (! large && ! local ) {
70+ # Base64 encode all files and send directly (for small files)
71+ file_parts <- lapply(pdf_path , function (path ) {
72+ list (
73+ inline_data = list (
74+ mime_type = mime_type ,
75+ data = base64enc :: base64encode(path )
76+ )
6177 )
62- )
63- })
78+ })
6479
65- # Add the prompt as the last part
66- parts <- c(file_parts , list (list (text = prompt )))
80+ parts <- c(file_parts , list (list (text = prompt )))
6781
68- # Prepare request body
69- body <- list (
70- contents = list (
71- list ( parts = parts )
82+ body <- list (
83+ contents = list (
84+ list (parts = parts )
85+ )
7286 )
73- )
7487
75- url <- " https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
88+ url <- " https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
7689
77- req <- httr2 :: request(url ) | >
78- httr2 :: req_url_query(key = api_key ) | >
79- httr2 :: req_headers(" Content-Type" = " application/json" ) | >
80- httr2 :: req_body_json(body , auto_unbox = TRUE )
90+ req <- httr2 :: request(url ) | >
91+ httr2 :: req_url_query(key = api_key ) | >
92+ httr2 :: req_headers(" Content-Type" = " application/json" ) | >
93+ httr2 :: req_body_json(body , auto_unbox = TRUE )
8194
82- resp <- httr2 :: req_perform(req )
95+ resp <- httr2 :: req_perform(req )
8396
84- if (resp $ status_code != 200 ) {
85- stop(paste0(" Error in Gemini API request: Status code " , resp $ status_code ))
86- }
97+ if (resp $ status_code != 200 ) {
98+ stop(paste0(" Error in Gemini API request: Status code " , resp $ status_code ))
99+ }
87100
88- result <- httr2 :: resp_body_json(resp )
101+ result <- httr2 :: resp_body_json(resp )
102+
103+ # Extract summary text (robust extraction of all $text fields)
104+ out <- tryCatch(
105+ {
106+ texts <- lapply(result $ candidates [[1 ]]$ content $ parts , function (part ) part $ text )
107+ texts <- texts [! sapply(texts , is.null )]
108+ paste(texts , collapse = " \n " )
109+ },
110+ error = function (e ) {
111+ paste(unlist(result $ candidates [[1 ]]$ content $ parts ), collapse = " \n " )
112+ }
113+ )
114+ return (out )
115+ } else {
116+ # Use file upload API for large or local files (only one file supported)
117+ if (length(pdf_path ) > 1 ) stop(" Large/Local mode supports only one file at a time." )
118+ file_uri <- upload_api(pdf_path [1 ], api_key , mime_type )
119+
120+ # Build request body for Gemini API using file_uri
121+ body <- list (
122+ contents = list (list (
123+ parts = list (
124+ list (text = prompt ),
125+ list (file_data = list (mime_type = mime_type , file_uri = file_uri ))
126+ )
127+ ))
128+ )
129+
130+ url <- " https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"
131+
132+ req <- httr2 :: request(url ) | >
133+ httr2 :: req_url_query(key = api_key ) | >
134+ httr2 :: req_headers(" Content-Type" = " application/json" ) | >
135+ httr2 :: req_body_json(body , auto_unbox = TRUE )
89136
90- # Extract summary text (robust extraction of all $text fields)
91- out <- tryCatch(
92- {
93- texts <- lapply(result $ candidates [[1 ]]$ content $ parts , function (part ) part $ text )
94- texts <- texts [! sapply(texts , is.null )]
95- paste(texts , collapse = " \n " )
96- },
97- error = function (e ) {
98- paste(unlist(result $ candidates [[1 ]]$ content $ parts ), collapse = " \n " )
137+ resp <- httr2 :: req_perform(req )
138+ if (resp $ status_code != 200 ) {
139+ stop(paste0(" Error in Gemini API request: Status code " , resp $ status_code ))
99140 }
100- )
101- return (out )
141+
142+ result <- httr2 :: resp_body_json(resp )
143+ out <- tryCatch(
144+ {
145+ texts <- lapply(result $ candidates [[1 ]]$ content $ parts , function (part ) part $ text )
146+ texts <- texts [! sapply(texts , is.null )]
147+ paste(texts , collapse = " \n " )
148+ },
149+ error = function (e ) {
150+ paste(unlist(result $ candidates [[1 ]]$ content $ parts ), collapse = " \n " )
151+ }
152+ )
153+ return (out )
154+ }
102155}
103156
104157# ' @title Summarize or analyze documents using Vertex AI Gemini
@@ -192,4 +245,46 @@ gemini_docs.vertex <- function(file_uri, prompt, mime_type = "application/pdf",
192245 candidates <- result $ candidates
193246 outputs <- unlist(lapply(candidates , function (candidate ) candidate $ content $ parts ))
194247 return (outputs )
248+ }
249+
250+ # Helper function for file upload API (Gemini/Vertex)
251+ upload_api <- function (local_file , api_key , mime_type ) {
252+ # Start resumable upload session
253+ file_size <- file.info(local_file )$ size
254+ meta_body <- list (file = list (display_name = basename(local_file )))
255+ meta_req <- httr2 :: request(" https://generativelanguage.googleapis.com/upload/v1beta/files" ) | >
256+ httr2 :: req_url_query(key = api_key ) | >
257+ httr2 :: req_headers(
258+ " X-Goog-Upload-Protocol" = " resumable" ,
259+ " X-Goog-Upload-Command" = " start" ,
260+ " X-Goog-Upload-Header-Content-Length" = as.character(file_size ),
261+ " X-Goog-Upload-Header-Content-Type" = mime_type ,
262+ " Content-Type" = " application/json"
263+ ) | >
264+ httr2 :: req_body_json(meta_body , auto_unbox = TRUE )
265+
266+ meta_resp <- httr2 :: req_perform(meta_req )
267+ if (meta_resp $ status_code != 200 ) {
268+ stop(paste0(" Error starting upload session: Status code " , meta_resp $ status_code ))
269+ }
270+ upload_url <- httr2 :: resp_headers(meta_resp )[[" x-goog-upload-url" ]]
271+ if (is.null(upload_url )) stop(" Failed to get upload URL from Gemini API." )
272+
273+ # Upload the file bytes
274+ upload_req <- httr2 :: request(upload_url ) | >
275+ httr2 :: req_headers(
276+ " Content-Length" = as.character(file_size ),
277+ " X-Goog-Upload-Offset" = " 0" ,
278+ " X-Goog-Upload-Command" = " upload, finalize"
279+ ) | >
280+ httr2 :: req_body_file(local_file )
281+
282+ upload_resp <- httr2 :: req_perform(upload_req )
283+ if (upload_resp $ status_code != 200 ) {
284+ stop(paste0(" Error uploading file: Status code " , upload_resp $ status_code ))
285+ }
286+ upload_result <- httr2 :: resp_body_json(upload_resp )
287+ file_uri <- upload_result $ file $ uri
288+ if (is.null(file_uri )) stop(" Failed to get file_uri after upload." )
289+ return (file_uri )
195290}
0 commit comments