google-gemini / generative-ai-python

The official Python library for the Google Gemini API
https://pypi.org/project/google-generativeai/
Apache License 2.0
1.18k stars 223 forks source link

[BUG] `407: Proxy Authentication Required` for `genai.upload_file` API while proxy has been set to ENV var #393

Open kuri-leo opened 1 week ago

kuri-leo commented 1 week ago

Description of the bug:

As user/pass have been set in ENV var as follows:

os.environ["HTTP_PROXY"] = "http://XXX:XXX@XXX.com:28888"
os.environ["HTTPS_PROXY"] = "http://XXX:XXX@XXX.com:28888"

The uploading API will raise an error from the following code, generated from AI studio:

def upload_to_gemini(path, mime_type=None):
    """Uploads the given file to Gemini.

    See https://ai.google.dev/gemini-api/docs/prompting_with_media
    """
    file = genai.upload_file(path, mime_type=mime_type)
    print(f"Uploaded file '{file.display_name}' as: {file.uri}")
    return file

The full error log as follows:

GeneralProxyError                         Traceback (most recent call last)
Cell In[4], line 16
      1 model = genai.GenerativeModel(
      2     model_name="gemini-1.5-pro",
      3     generation_config=generation_config,
   (...)
     10     # See https://ai.google.dev/gemini-api/docs/safety-settings
     11     )
     13 # TODO Make these files available on the local file system
     14 # You may need to update the file paths
     15 files = [
---> 16     upload_to_gemini("XXX.png",
     17                      mime_type="image/png"),
     18     ]
     20 chat_session = model.start_chat(
     21     history=[
     22         {
   (...)
     28         ]
     29     )
     31 response = chat_session.send_message("Please describe the given image in detail.")

Cell In[3], line 12, in upload_to_gemini(path, mime_type)
      7 def upload_to_gemini(path, mime_type=None):
      8     """Uploads the given file to Gemini.
      9   
     10     See https://ai.google.dev/gemini-api/docs/prompting_with_media
     11     """
---> 12     file = genai.upload_file(path, mime_type=mime_type)
     13     print(f"Uploaded file '{file.display_name}' as: {file.uri}")
     14     return file

File ~/.miniconda/envs/jupyter/lib/python3.10/site-packages/google/generativeai/files.py:69, in upload_file(path, mime_type, name, display_name, resumable)
     66 if display_name is None:
     67     display_name = path.name
---> 69 response = client.create_file(
     70     path=path, mime_type=mime_type, name=name, display_name=display_name, resumable=resumable
     71 )
     72 return file_types.File(response)

File ~/.miniconda/envs/jupyter/lib/python3.10/site-packages/google/generativeai/client.py:82, in FileServiceClient.create_file(self, path, mime_type, name, display_name, resumable)
     72 def create_file(
     73     self,
     74     path: str | pathlib.Path | os.PathLike,
   (...)
     79     resumable: bool = True,
     80 ) -> protos.File:
     81     if self._discovery_api is None:
---> 82         self._setup_discovery_api()
     84     file = {}
     85     if name is not None:

File ~/.miniconda/envs/jupyter/lib/python3.10/site-packages/google/generativeai/client.py:65, in FileServiceClient._setup_discovery_api(self)
     56     raise ValueError(
     57         "Invalid operation: Uploading to the File API requires an API key. Please provide a valid API key."
     58     )
     60 request = googleapiclient.http.HttpRequest(
     61     http=httplib2.Http(),
     62     postproc=lambda resp, content: (resp, content),
     63     uri=f"{GENAI_API_DISCOVERY_URL}?version=v1beta&key={api_key}",
     64 )
---> 65 response, content = request.execute()
     67 discovery_doc = content.decode("utf-8")
     68 self._discovery_api = googleapiclient.discovery.build_from_document(
     69     discovery_doc, developerKey=api_key
     70 )

File ~/.miniconda/envs/jupyter/lib/python3.10/site-packages/googleapiclient/_helpers.py:130, in positional.<locals>.positional_decorator.<locals>.positional_wrapper(*args, **kwargs)
    128     elif positional_parameters_enforcement == POSITIONAL_WARNING:
    129         logger.warning(message)
--> 130 return wrapped(*args, **kwargs)

File ~/.miniconda/envs/jupyter/lib/python3.10/site-packages/googleapiclient/http.py:923, in HttpRequest.execute(self, http, num_retries)
    920     self.headers["content-length"] = str(len(self.body))
    922 # Handle retries for server-side errors.
--> 923 resp, content = _retry_request(
    924     http,
    925     num_retries,
    926     "request",
    927     self._sleep,
    928     self._rand,
    929     str(self.uri),
    930     method=str(self.method),
    931     body=self.body,
    932     headers=self.headers,
    933 )
    935 for callback in self.response_callbacks:
    936     callback(resp)

File ~/.miniconda/envs/jupyter/lib/python3.10/site-packages/googleapiclient/http.py:191, in _retry_request(http, num_retries, req_type, sleep, rand, uri, method, *args, **kwargs)
    189 try:
    190     exception = None
--> 191     resp, content = http.request(uri, method, *args, **kwargs)
    192 # Retry on SSL errors and socket timeout errors.
    193 except _ssl_SSLError as ssl_error:

File ~/.miniconda/envs/jupyter/lib/python3.10/site-packages/httplib2/__init__.py:1724, in Http.request(self, uri, method, body, headers, redirections, connection_type)
   1722             content = b""
   1723         else:
-> 1724             (response, content) = self._request(
   1725                 conn, authority, uri, request_uri, method, body, headers, redirections, cachekey,
   1726             )
   1727 except Exception as e:
   1728     is_timeout = isinstance(e, socket.timeout)

File ~/.miniconda/envs/jupyter/lib/python3.10/site-packages/httplib2/__init__.py:1444, in Http._request(self, conn, host, absolute_uri, request_uri, method, body, headers, redirections, cachekey)
   1441 if auth:
   1442     auth.request(method, request_uri, headers, body)
-> 1444 (response, content) = self._conn_request(conn, request_uri, method, body, headers)
   1446 if auth:
   1447     if auth.response(response, body):

File ~/.miniconda/envs/jupyter/lib/python3.10/site-packages/httplib2/__init__.py:1366, in Http._conn_request(self, conn, request_uri, method, body, headers)
   1364 try:
   1365     if conn.sock is None:
-> 1366         conn.connect()
   1367     conn.request(method, request_uri, body, headers)
   1368 except socket.timeout:

File ~/.miniconda/envs/jupyter/lib/python3.10/site-packages/httplib2/__init__.py:1202, in HTTPSConnectionWithTimeout.connect(self)
   1200     break
   1201 if not self.sock:
-> 1202     raise socket_err

File ~/.miniconda/envs/jupyter/lib/python3.10/site-packages/httplib2/__init__.py:1156, in HTTPSConnectionWithTimeout.connect(self)
   1154 if has_timeout(self.timeout):
   1155     sock.settimeout(self.timeout)
-> 1156 sock.connect((self.host, self.port))
   1158 self.sock = self._context.wrap_socket(sock, server_hostname=self.host)
   1160 # Python 3.3 compatibility: emulate the check_hostname behavior

File ~/.miniconda/envs/jupyter/lib/python3.10/site-packages/socks.py:47, in set_self_blocking.<locals>.wrapper(*args, **kwargs)
     45     if _is_blocking == 0:
     46         self.setblocking(True)
---> 47     return function(*args, **kwargs)
     48 except Exception as e:
     49     raise

File ~/.miniconda/envs/jupyter/lib/python3.10/site-packages/socks.py:814, in socksocket.connect(self, dest_pair, catch_errors)
    811 if not catch_errors:
    812     # Wrap socket errors
    813     self.close()
--> 814     raise GeneralProxyError("Socket error", error)
    815 else:
    816     raise error

GeneralProxyError: Socket error: 407: Proxy Authentication Required

Actual vs expected behavior:

Go through an http proxy with authentication correctly, but, it fails.

Any other information you'd like to share?

Maybe related issues in upstream:

While everything works fine with these packages alone:

from urllib.parse import urlparse
url = urlparse("http://USER:PASSWORD@HOST:PORT/PATH")
print(url)
print(url.username)
print(url.password)

It returns:

ParseResult(scheme='http', netloc='USER:PASSWORD@HOST:PORT', path='/PATH', params='', query='', fragment='')
USER
PASSWORD

And:

import httplib2

pi = httplib2.proxy_info_from_url("http://USER:PASSWORD@HOST:80/PATH")
print(pi)
print(pi.proxy_host)
print(pi.proxy_port)
print(pi.proxy_user)
print(pi.proxy_pass)

it returns

<ProxyInfo type=3 host:port=host:80 rdns=True user=USER headers=None>
host
80
USER
PASSWORD
singhniraj08 commented 1 week ago

@kuri-leo, I can see "Invalid operation: Uploading to the File API requires an API key. Please provide a valid API key." error in the provided error log. Can you please make sure you are passing a valid Gemini API key? Thank you!

kuri-leo commented 1 week ago

@kuri-leo, I can see "Invalid operation: Uploading to the File API requires an API key. Please provide a valid API key." error in the provided error log. Can you please make sure you are passing a valid Gemini API key? Thank you!

Hi @singhniraj08 , thanks for your follow-up.

I've verified that my Gemini API key is valid. The same key works perfectly with the RESTful endpoint using the same proxy and also on another server with tproxy.

While the error Proxy Authentication Required is unexpected here as use and pass have been set in ENV var, that's quite interesting.