Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

openai-python v1.2.3 #4292

Closed
psymbio opened this issue Nov 13, 2023 · 24 comments
Closed

openai-python v1.2.3 #4292

psymbio opened this issue Nov 13, 2023 · 24 comments

Comments

@psymbio
Copy link

psymbio commented Nov 13, 2023

So, Openai in the latest version of their library openai-python shifted from requests to httpx (openai/openai-python#742 and https://github.com/openai/openai-python/blob/main/README.md). I wanted to understand what's the best possible way to make this work with Pyodide?

  1. Should I consider rolling back to v0.28.0 which uses requests?
    Create the wheel:

    git clone --branch release-v0.28.1 https://github.com/openai/openai-python.git
    cd openai-python/
    python3 setup.py bdist_wheel

    Use it on JupyterLite:

    import micropip
    await micropip.install('https://github.com/psymbio/tiktoken_rust_wasm/blob/main/packages/openai_wheel/openai-0.28.1-py3-none-any.whl')

    I get the BadZipFile error for this.

  2. Should I consider patching the current library to use requests wherever httpx is used (this is a lot of places - and I'm not sure if this is possible to do, also if their library updates this means I will have to as well).

  3. Will this work as-is if I create a wheel and install it?
    Currently this fails for me; To recreate this run the following:

    git clone https://github.com/openai/openai-python.git
    cd openai-python/
    python -m pip install build
    python -m build

    This create a .whl file in the dist/ directory, which I then upload to https://jupyter.org/try-jupyter/lab/ and write the following code:

    import micropip
    await micropip.install('openai-1.2.3-py3-none-any.whl')

    If this gives an error: BadZipFile try this instead:

    1. Enable https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf a Chrome extension
    2. Pin the extension and watch the YouTube tutorial: https://www.youtube.com/watch?v=KruSUqLdxQA&ab_channel=eccoremproject
    3. You have to Toggle the extension to be on.
    4. Go to settings by clicking on the extension and then "Open options page"
    5. Here under Access-Control-Allow-Origin - select "*" instead of "ORIGIN".
    6. Now, click on the extension and "Reload the active tab" to use the extension.
      Run the following code on JupyterLite
    import micropip
    await micropip.install('https://github.com/psymbio/tiktoken_rust_wasm/blob/main/packages/openai_wheel/openai-1.2.3-py3-none-any.whl')

    However, I still get the same BadZipFile error. Not entirely sure what triggers it, for some packages like (https://github.com/psymbio/tiktoken_rust_wasm/blob/main/packages/build_emscripten_cached_weights/tiktoken-0.5.1-cp311-cp311-emscripten_3_1_45_wasm32.whl) downloading the wheel from Github instead of placing the wheel in JupyterLite, seems to be the fix.

    Another way to get it to work is:

    import micropip
    await micropip.install('openai', keep_going=True)
    await micropip.install("ssl")
    import openai
    from openai import OpenAI
    
    client = OpenAI(
        api_key="API KEY",
    )
    
    chat_completion = client.chat.completions.create(
        messages=[
            {
                "role": "user",
                "content": "Say this is a test",
            }
        ],
        model="gpt-3.5-turbo",
    )

    This results in the real issue the package doesn't work:

        OSError                                   Traceback (most recent call last)
    File /lib/python3.11/site-packages/httpcore/_exceptions.py:10, in map_exceptions(map)
        9 try:
    ---> 10     yield
        11 except Exception as exc:  # noqa: PIE786
    
    File /lib/python3.11/site-packages/httpcore/_backends/sync.py:206, in SyncBackend.connect_tcp(self, host, port, timeout, local_address, socket_options)
        205 with map_exceptions(exc_map):
    --> 206     sock = socket.create_connection(
        207         address,
        208         timeout,
        209         source_address=source_address,
        210     )
        211     for option in socket_options:
    
    File /lib/python311.zip/socket.py:851, in create_connection(address, timeout, source_address, all_errors)
        850 if not all_errors:
    --> 851     raise exceptions[0]
        852 raise ExceptionGroup("create_connection failed", exceptions)
    
    File /lib/python311.zip/socket.py:836, in create_connection(address, timeout, source_address, all_errors)
        835     sock.bind(source_address)
    --> 836 sock.connect(sa)
        837 # Break explicitly a reference cycle
    
    OSError: [Errno 23] Host is unreachable
    
    The above exception was the direct cause of the following exception:
    
    ConnectError                              Traceback (most recent call last)
    File /lib/python3.11/site-packages/httpx/_transports/default.py:66, in map_httpcore_exceptions()
        65 try:
    ---> 66     yield
        67 except Exception as exc:
    
    File /lib/python3.11/site-packages/httpx/_transports/default.py:228, in HTTPTransport.handle_request(self, request)
        227 with map_httpcore_exceptions():
    --> 228     resp = self._pool.handle_request(req)
        230 assert isinstance(resp.stream, typing.Iterable)
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection_pool.py:268, in ConnectionPool.handle_request(self, request)
        267         self.response_closed(status)
    --> 268     raise exc
        269 else:
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection_pool.py:251, in ConnectionPool.handle_request(self, request)
        250 try:
    --> 251     response = connection.handle_request(request)
        252 except ConnectionNotAvailable:
        253     # The ConnectionNotAvailable exception is a special case, that
        254     # indicates we need to retry the request on a new connection.
    (...)
        258     # might end up as an HTTP/2 connection, but which actually ends
        259     # up as HTTP/1.1.
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection.py:99, in HTTPConnection.handle_request(self, request)
        98         self._connect_failed = True
    ---> 99         raise exc
        100 elif not self._connection.is_available():
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection.py:76, in HTTPConnection.handle_request(self, request)
        75 try:
    ---> 76     stream = self._connect(request)
        78     ssl_object = stream.get_extra_info("ssl_object")
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection.py:124, in HTTPConnection._connect(self, request)
        123 with Trace("connect_tcp", logger, request, kwargs) as trace:
    --> 124     stream = self._network_backend.connect_tcp(**kwargs)
        125     trace.return_value = stream
    
    File /lib/python3.11/site-packages/httpcore/_backends/sync.py:205, in SyncBackend.connect_tcp(self, host, port, timeout, local_address, socket_options)
        200 exc_map: ExceptionMapping = {
        201     socket.timeout: ConnectTimeout,
        202     OSError: ConnectError,
        203 }
    --> 205 with map_exceptions(exc_map):
        206     sock = socket.create_connection(
        207         address,
        208         timeout,
        209         source_address=source_address,
        210     )
    
    File /lib/python311.zip/contextlib.py:155, in _GeneratorContextManager.__exit__(self, typ, value, traceback)
        154 try:
    --> 155     self.gen.throw(typ, value, traceback)
        156 except StopIteration as exc:
        157     # Suppress StopIteration *unless* it's the same exception that
        158     # was passed to throw().  This prevents a StopIteration
        159     # raised inside the "with" statement from being suppressed.
    
    File /lib/python3.11/site-packages/httpcore/_exceptions.py:14, in map_exceptions(map)
        13     if isinstance(exc, from_exc):
    ---> 14         raise to_exc(exc) from exc
        15 raise
    
    ConnectError: [Errno 23] Host is unreachable
    
    The above exception was the direct cause of the following exception:
    
    ConnectError                              Traceback (most recent call last)
    File /lib/python3.11/site-packages/openai/_base_client.py:858, in SyncAPIClient._request(self, cast_to, options, remaining_retries, stream, stream_cls)
        857 try:
    --> 858     response = self._client.send(request, auth=self.custom_auth, stream=stream)
        859     log.debug(
        860         'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase
        861     )
    
    File /lib/python3.11/site-packages/httpx/_client.py:901, in Client.send(self, request, stream, auth, follow_redirects)
        899 auth = self._build_request_auth(request, auth)
    --> 901 response = self._send_handling_auth(
        902     request,
        903     auth=auth,
        904     follow_redirects=follow_redirects,
        905     history=[],
        906 )
        907 try:
    
    File /lib/python3.11/site-packages/httpx/_client.py:929, in Client._send_handling_auth(self, request, auth, follow_redirects, history)
        928 while True:
    --> 929     response = self._send_handling_redirects(
        930         request,
        931         follow_redirects=follow_redirects,
        932         history=history,
        933     )
        934     try:
    
    File /lib/python3.11/site-packages/httpx/_client.py:966, in Client._send_handling_redirects(self, request, follow_redirects, history)
        964     hook(request)
    --> 966 response = self._send_single_request(request)
        967 try:
    
    File /lib/python3.11/site-packages/httpx/_client.py:1002, in Client._send_single_request(self, request)
    1001 with request_context(request=request):
    -> 1002     response = transport.handle_request(request)
    1004 assert isinstance(response.stream, SyncByteStream)
    
    File /lib/python3.11/site-packages/httpx/_transports/default.py:227, in HTTPTransport.handle_request(self, request)
        215 req = httpcore.Request(
        216     method=request.method,
        217     url=httpcore.URL(
    (...)
        225     extensions=request.extensions,
        226 )
    --> 227 with map_httpcore_exceptions():
        228     resp = self._pool.handle_request(req)
    
    File /lib/python311.zip/contextlib.py:155, in _GeneratorContextManager.__exit__(self, typ, value, traceback)
        154 try:
    --> 155     self.gen.throw(typ, value, traceback)
        156 except StopIteration as exc:
        157     # Suppress StopIteration *unless* it's the same exception that
        158     # was passed to throw().  This prevents a StopIteration
        159     # raised inside the "with" statement from being suppressed.
    
    File /lib/python3.11/site-packages/httpx/_transports/default.py:83, in map_httpcore_exceptions()
        82 message = str(exc)
    ---> 83 raise mapped_exc(message) from exc
    
    ConnectError: [Errno 23] Host is unreachable
    
    During handling of the above exception, another exception occurred:
    
    OSError                                   Traceback (most recent call last)
    File /lib/python3.11/site-packages/httpcore/_exceptions.py:10, in map_exceptions(map)
        9 try:
    ---> 10     yield
        11 except Exception as exc:  # noqa: PIE786
    
    File /lib/python3.11/site-packages/httpcore/_backends/sync.py:206, in SyncBackend.connect_tcp(self, host, port, timeout, local_address, socket_options)
        205 with map_exceptions(exc_map):
    --> 206     sock = socket.create_connection(
        207         address,
        208         timeout,
        209         source_address=source_address,
        210     )
        211     for option in socket_options:
    
    File /lib/python311.zip/socket.py:851, in create_connection(address, timeout, source_address, all_errors)
        850 if not all_errors:
    --> 851     raise exceptions[0]
        852 raise ExceptionGroup("create_connection failed", exceptions)
    
    File /lib/python311.zip/socket.py:836, in create_connection(address, timeout, source_address, all_errors)
        835     sock.bind(source_address)
    --> 836 sock.connect(sa)
        837 # Break explicitly a reference cycle
    
    OSError: [Errno 23] Host is unreachable
    
    The above exception was the direct cause of the following exception:
    
    ConnectError                              Traceback (most recent call last)
    File /lib/python3.11/site-packages/httpx/_transports/default.py:66, in map_httpcore_exceptions()
        65 try:
    ---> 66     yield
        67 except Exception as exc:
    
    File /lib/python3.11/site-packages/httpx/_transports/default.py:228, in HTTPTransport.handle_request(self, request)
        227 with map_httpcore_exceptions():
    --> 228     resp = self._pool.handle_request(req)
        230 assert isinstance(resp.stream, typing.Iterable)
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection_pool.py:268, in ConnectionPool.handle_request(self, request)
        267         self.response_closed(status)
    --> 268     raise exc
        269 else:
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection_pool.py:251, in ConnectionPool.handle_request(self, request)
        250 try:
    --> 251     response = connection.handle_request(request)
        252 except ConnectionNotAvailable:
        253     # The ConnectionNotAvailable exception is a special case, that
        254     # indicates we need to retry the request on a new connection.
    (...)
        258     # might end up as an HTTP/2 connection, but which actually ends
        259     # up as HTTP/1.1.
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection.py:99, in HTTPConnection.handle_request(self, request)
        98         self._connect_failed = True
    ---> 99         raise exc
        100 elif not self._connection.is_available():
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection.py:76, in HTTPConnection.handle_request(self, request)
        75 try:
    ---> 76     stream = self._connect(request)
        78     ssl_object = stream.get_extra_info("ssl_object")
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection.py:124, in HTTPConnection._connect(self, request)
        123 with Trace("connect_tcp", logger, request, kwargs) as trace:
    --> 124     stream = self._network_backend.connect_tcp(**kwargs)
        125     trace.return_value = stream
    
    File /lib/python3.11/site-packages/httpcore/_backends/sync.py:205, in SyncBackend.connect_tcp(self, host, port, timeout, local_address, socket_options)
        200 exc_map: ExceptionMapping = {
        201     socket.timeout: ConnectTimeout,
        202     OSError: ConnectError,
        203 }
    --> 205 with map_exceptions(exc_map):
        206     sock = socket.create_connection(
        207         address,
        208         timeout,
        209         source_address=source_address,
        210     )
    
    File /lib/python311.zip/contextlib.py:155, in _GeneratorContextManager.__exit__(self, typ, value, traceback)
        154 try:
    --> 155     self.gen.throw(typ, value, traceback)
        156 except StopIteration as exc:
        157     # Suppress StopIteration *unless* it's the same exception that
        158     # was passed to throw().  This prevents a StopIteration
        159     # raised inside the "with" statement from being suppressed.
    
    File /lib/python3.11/site-packages/httpcore/_exceptions.py:14, in map_exceptions(map)
        13     if isinstance(exc, from_exc):
    ---> 14         raise to_exc(exc) from exc
        15 raise
    
    ConnectError: [Errno 23] Host is unreachable
    
    The above exception was the direct cause of the following exception:
    
    ConnectError                              Traceback (most recent call last)
    File /lib/python3.11/site-packages/openai/_base_client.py:858, in SyncAPIClient._request(self, cast_to, options, remaining_retries, stream, stream_cls)
        857 try:
    --> 858     response = self._client.send(request, auth=self.custom_auth, stream=stream)
        859     log.debug(
        860         'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase
        861     )
    
    File /lib/python3.11/site-packages/httpx/_client.py:901, in Client.send(self, request, stream, auth, follow_redirects)
        899 auth = self._build_request_auth(request, auth)
    --> 901 response = self._send_handling_auth(
        902     request,
        903     auth=auth,
        904     follow_redirects=follow_redirects,
        905     history=[],
        906 )
        907 try:
    
    File /lib/python3.11/site-packages/httpx/_client.py:929, in Client._send_handling_auth(self, request, auth, follow_redirects, history)
        928 while True:
    --> 929     response = self._send_handling_redirects(
        930         request,
        931         follow_redirects=follow_redirects,
        932         history=history,
        933     )
        934     try:
    
    File /lib/python3.11/site-packages/httpx/_client.py:966, in Client._send_handling_redirects(self, request, follow_redirects, history)
        964     hook(request)
    --> 966 response = self._send_single_request(request)
        967 try:
    
    File /lib/python3.11/site-packages/httpx/_client.py:1002, in Client._send_single_request(self, request)
    1001 with request_context(request=request):
    -> 1002     response = transport.handle_request(request)
    1004 assert isinstance(response.stream, SyncByteStream)
    
    File /lib/python3.11/site-packages/httpx/_transports/default.py:227, in HTTPTransport.handle_request(self, request)
        215 req = httpcore.Request(
        216     method=request.method,
        217     url=httpcore.URL(
    (...)
        225     extensions=request.extensions,
        226 )
    --> 227 with map_httpcore_exceptions():
        228     resp = self._pool.handle_request(req)
    
    File /lib/python311.zip/contextlib.py:155, in _GeneratorContextManager.__exit__(self, typ, value, traceback)
        154 try:
    --> 155     self.gen.throw(typ, value, traceback)
        156 except StopIteration as exc:
        157     # Suppress StopIteration *unless* it's the same exception that
        158     # was passed to throw().  This prevents a StopIteration
        159     # raised inside the "with" statement from being suppressed.
    
    File /lib/python3.11/site-packages/httpx/_transports/default.py:83, in map_httpcore_exceptions()
        82 message = str(exc)
    ---> 83 raise mapped_exc(message) from exc
    
    ConnectError: [Errno 23] Host is unreachable
    
    During handling of the above exception, another exception occurred:
    
    OSError                                   Traceback (most recent call last)
    File /lib/python3.11/site-packages/httpcore/_exceptions.py:10, in map_exceptions(map)
        9 try:
    ---> 10     yield
        11 except Exception as exc:  # noqa: PIE786
    
    File /lib/python3.11/site-packages/httpcore/_backends/sync.py:206, in SyncBackend.connect_tcp(self, host, port, timeout, local_address, socket_options)
        205 with map_exceptions(exc_map):
    --> 206     sock = socket.create_connection(
        207         address,
        208         timeout,
        209         source_address=source_address,
        210     )
        211     for option in socket_options:
    
    File /lib/python311.zip/socket.py:851, in create_connection(address, timeout, source_address, all_errors)
        850 if not all_errors:
    --> 851     raise exceptions[0]
        852 raise ExceptionGroup("create_connection failed", exceptions)
    
    File /lib/python311.zip/socket.py:836, in create_connection(address, timeout, source_address, all_errors)
        835     sock.bind(source_address)
    --> 836 sock.connect(sa)
        837 # Break explicitly a reference cycle
    
    OSError: [Errno 23] Host is unreachable
    
    The above exception was the direct cause of the following exception:
    
    ConnectError                              Traceback (most recent call last)
    File /lib/python3.11/site-packages/httpx/_transports/default.py:66, in map_httpcore_exceptions()
        65 try:
    ---> 66     yield
        67 except Exception as exc:
    
    File /lib/python3.11/site-packages/httpx/_transports/default.py:228, in HTTPTransport.handle_request(self, request)
        227 with map_httpcore_exceptions():
    --> 228     resp = self._pool.handle_request(req)
        230 assert isinstance(resp.stream, typing.Iterable)
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection_pool.py:268, in ConnectionPool.handle_request(self, request)
        267         self.response_closed(status)
    --> 268     raise exc
        269 else:
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection_pool.py:251, in ConnectionPool.handle_request(self, request)
        250 try:
    --> 251     response = connection.handle_request(request)
        252 except ConnectionNotAvailable:
        253     # The ConnectionNotAvailable exception is a special case, that
        254     # indicates we need to retry the request on a new connection.
    (...)
        258     # might end up as an HTTP/2 connection, but which actually ends
        259     # up as HTTP/1.1.
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection.py:99, in HTTPConnection.handle_request(self, request)
        98         self._connect_failed = True
    ---> 99         raise exc
        100 elif not self._connection.is_available():
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection.py:76, in HTTPConnection.handle_request(self, request)
        75 try:
    ---> 76     stream = self._connect(request)
        78     ssl_object = stream.get_extra_info("ssl_object")
    
    File /lib/python3.11/site-packages/httpcore/_sync/connection.py:124, in HTTPConnection._connect(self, request)
        123 with Trace("connect_tcp", logger, request, kwargs) as trace:
    --> 124     stream = self._network_backend.connect_tcp(**kwargs)
        125     trace.return_value = stream
    
    File /lib/python3.11/site-packages/httpcore/_backends/sync.py:205, in SyncBackend.connect_tcp(self, host, port, timeout, local_address, socket_options)
        200 exc_map: ExceptionMapping = {
        201     socket.timeout: ConnectTimeout,
        202     OSError: ConnectError,
        203 }
    --> 205 with map_exceptions(exc_map):
        206     sock = socket.create_connection(
        207         address,
        208         timeout,
        209         source_address=source_address,
        210     )
    
    File /lib/python311.zip/contextlib.py:155, in _GeneratorContextManager.__exit__(self, typ, value, traceback)
        154 try:
    --> 155     self.gen.throw(typ, value, traceback)
        156 except StopIteration as exc:
        157     # Suppress StopIteration *unless* it's the same exception that
        158     # was passed to throw().  This prevents a StopIteration
        159     # raised inside the "with" statement from being suppressed.
    
    File /lib/python3.11/site-packages/httpcore/_exceptions.py:14, in map_exceptions(map)
        13     if isinstance(exc, from_exc):
    ---> 14         raise to_exc(exc) from exc
        15 raise
    
    ConnectError: [Errno 23] Host is unreachable
    
    The above exception was the direct cause of the following exception:
    
    ConnectError                              Traceback (most recent call last)
    File /lib/python3.11/site-packages/openai/_base_client.py:858, in SyncAPIClient._request(self, cast_to, options, remaining_retries, stream, stream_cls)
        857 try:
    --> 858     response = self._client.send(request, auth=self.custom_auth, stream=stream)
        859     log.debug(
        860         'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase
        861     )
    
    File /lib/python3.11/site-packages/httpx/_client.py:901, in Client.send(self, request, stream, auth, follow_redirects)
        899 auth = self._build_request_auth(request, auth)
    --> 901 response = self._send_handling_auth(
        902     request,
        903     auth=auth,
        904     follow_redirects=follow_redirects,
        905     history=[],
        906 )
        907 try:
    
    File /lib/python3.11/site-packages/httpx/_client.py:929, in Client._send_handling_auth(self, request, auth, follow_redirects, history)
        928 while True:
    --> 929     response = self._send_handling_redirects(
        930         request,
        931         follow_redirects=follow_redirects,
        932         history=history,
        933     )
        934     try:
    
    File /lib/python3.11/site-packages/httpx/_client.py:966, in Client._send_handling_redirects(self, request, follow_redirects, history)
        964     hook(request)
    --> 966 response = self._send_single_request(request)
        967 try:
    
    File /lib/python3.11/site-packages/httpx/_client.py:1002, in Client._send_single_request(self, request)
    1001 with request_context(request=request):
    -> 1002     response = transport.handle_request(request)
    1004 assert isinstance(response.stream, SyncByteStream)
    
    File /lib/python3.11/site-packages/httpx/_transports/default.py:227, in HTTPTransport.handle_request(self, request)
        215 req = httpcore.Request(
        216     method=request.method,
        217     url=httpcore.URL(
    (...)
        225     extensions=request.extensions,
        226 )
    --> 227 with map_httpcore_exceptions():
        228     resp = self._pool.handle_request(req)
    
    File /lib/python311.zip/contextlib.py:155, in _GeneratorContextManager.__exit__(self, typ, value, traceback)
        154 try:
    --> 155     self.gen.throw(typ, value, traceback)
        156 except StopIteration as exc:
        157     # Suppress StopIteration *unless* it's the same exception that
        158     # was passed to throw().  This prevents a StopIteration
        159     # raised inside the "with" statement from being suppressed.
    
    File /lib/python3.11/site-packages/httpx/_transports/default.py:83, in map_httpcore_exceptions()
        82 message = str(exc)
    ---> 83 raise mapped_exc(message) from exc
    
    ConnectError: [Errno 23] Host is unreachable
    
    The above exception was the direct cause of the following exception:
    
    APIConnectionError                        Traceback (most recent call last)
    Cell In[7], line 11
        5 from openai import OpenAI
        7 client = OpenAI(
        8     api_key="API KEY",
        9 )
    ---> 11 chat_completion = client.chat.completions.create(
        12     messages=[
        13         {
        14             "role": "user",
        15             "content": "Say this is a test",
        16         }
        17     ],
        18     model="gpt-3.5-turbo",
        19 )
    
    File /lib/python3.11/site-packages/openai/_utils/_utils.py:299, in required_args.<locals>.inner.<locals>.wrapper(*args, **kwargs)
        297             msg = f"Missing required argument: {quote(missing[0])}"
        298     raise TypeError(msg)
    --> 299 return func(*args, **kwargs)
    
    File /lib/python3.11/site-packages/openai/resources/chat/completions.py:594, in Completions.create(self, messages, model, frequency_penalty, function_call, functions, logit_bias, max_tokens, n, presence_penalty, response_format, seed, stop, stream, temperature, tool_choice, tools, top_p, user, extra_headers, extra_query, extra_body, timeout)
        548 @required_args(["messages", "model"], ["messages", "model", "stream"])
        549 def create(
        550     self,
    (...)
        592     timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
        593 ) -> ChatCompletion | Stream[ChatCompletionChunk]:
    --> 594     return self._post(
        595         "/chat/completions",
        596         body=maybe_transform(
        597             {
        598                 "messages": messages,
        599                 "model": model,
        600                 "frequency_penalty": frequency_penalty,
        601                 "function_call": function_call,
        602                 "functions": functions,
        603                 "logit_bias": logit_bias,
        604                 "max_tokens": max_tokens,
        605                 "n": n,
        606                 "presence_penalty": presence_penalty,
        607                 "response_format": response_format,
        608                 "seed": seed,
        609                 "stop": stop,
        610                 "stream": stream,
        611                 "temperature": temperature,
        612                 "tool_choice": tool_choice,
        613                 "tools": tools,
        614                 "top_p": top_p,
        615                 "user": user,
        616             },
        617             completion_create_params.CompletionCreateParams,
        618         ),
        619         options=make_request_options(
        620             extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
        621         ),
        622         cast_to=ChatCompletion,
        623         stream=stream or False,
        624         stream_cls=Stream[ChatCompletionChunk],
        625     )
    
    File /lib/python3.11/site-packages/openai/_base_client.py:1055, in SyncAPIClient.post(self, path, cast_to, body, options, files, stream, stream_cls)
    1041 def post(
    1042     self,
    1043     path: str,
    (...)
    1050     stream_cls: type[_StreamT] | None = None,
    1051 ) -> ResponseT | _StreamT:
    1052     opts = FinalRequestOptions.construct(
    1053         method="post", url=path, json_data=body, files=to_httpx_files(files), **options
    1054     )
    -> 1055     return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
    
    File /lib/python3.11/site-packages/openai/_base_client.py:834, in SyncAPIClient.request(self, cast_to, options, remaining_retries, stream, stream_cls)
        825 def request(
        826     self,
        827     cast_to: Type[ResponseT],
    (...)
        832     stream_cls: type[_StreamT] | None = None,
        833 ) -> ResponseT | _StreamT:
    --> 834     return self._request(
        835         cast_to=cast_to,
        836         options=options,
        837         stream=stream,
        838         stream_cls=stream_cls,
        839         remaining_retries=remaining_retries,
        840     )
    
    File /lib/python3.11/site-packages/openai/_base_client.py:890, in SyncAPIClient._request(self, cast_to, options, remaining_retries, stream, stream_cls)
        888 except Exception as err:
        889     if retries > 0:
    --> 890         return self._retry_request(
        891             options,
        892             cast_to,
        893             retries,
        894             stream=stream,
        895             stream_cls=stream_cls,
        896         )
        897     raise APIConnectionError(request=request) from err
        899 return self._process_response(
        900     cast_to=cast_to,
        901     options=options,
    (...)
        904     stream_cls=stream_cls,
        905 )
    
    File /lib/python3.11/site-packages/openai/_base_client.py:925, in SyncAPIClient._retry_request(self, options, cast_to, remaining_retries, response_headers, stream, stream_cls)
        921 # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
        922 # different thread if necessary.
        923 time.sleep(timeout)
    --> 925 return self._request(
        926     options=options,
        927     cast_to=cast_to,
        928     remaining_retries=remaining,
        929     stream=stream,
        930     stream_cls=stream_cls,
        931 )
    
    File /lib/python3.11/site-packages/openai/_base_client.py:890, in SyncAPIClient._request(self, cast_to, options, remaining_retries, stream, stream_cls)
        888 except Exception as err:
        889     if retries > 0:
    --> 890         return self._retry_request(
        891             options,
        892             cast_to,
        893             retries,
        894             stream=stream,
        895             stream_cls=stream_cls,
        896         )
        897     raise APIConnectionError(request=request) from err
        899 return self._process_response(
        900     cast_to=cast_to,
        901     options=options,
    (...)
        904     stream_cls=stream_cls,
        905 )
    
    File /lib/python3.11/site-packages/openai/_base_client.py:925, in SyncAPIClient._retry_request(self, options, cast_to, remaining_retries, response_headers, stream, stream_cls)
        921 # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
        922 # different thread if necessary.
        923 time.sleep(timeout)
    --> 925 return self._request(
        926     options=options,
        927     cast_to=cast_to,
        928     remaining_retries=remaining,
        929     stream=stream,
        930     stream_cls=stream_cls,
        931 )
    
    File /lib/python3.11/site-packages/openai/_base_client.py:897, in SyncAPIClient._request(self, cast_to, options, remaining_retries, stream, stream_cls)
        889     if retries > 0:
        890         return self._retry_request(
        891             options,
        892             cast_to,
    (...)
        895             stream_cls=stream_cls,
        896         )
    --> 897     raise APIConnectionError(request=request) from err
        899 return self._process_response(
        900     cast_to=cast_to,
        901     options=options,
    (...)
        904     stream_cls=stream_cls,
        905 )
    
    APIConnectionError: Connection error.
  4. Doing something simpler like calling the APIs myself/creating a simpler library that does this:

    import micropip
    await micropip.install('pyodide-http')
    await micropip.install('requests')
    import pyodide_http
    pyodide_http.patch_all()
    import requests
    import json
    
    url = "https://api.openai.com/v1/completions"
    
    payload = json.dumps({
    "model": "gpt-3.5-turbo-instruct",
    "prompt": "Tell me a joke, I'm bored",
    "max_tokens": 10,
    "temperature": 0.7
    })
    headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <YOUR-OPENAI-KEY>',
    'Cookie': 'some cookie'
    }
    
    response = requests.request("POST", url, headers=headers, data=payload)
    print(response.text)

Also, not able to tell whether this error should just be titled: BadZipFile error instead. Thanks for looking into this, in advance.

🐍 Package Request

  • Package Name and Version : openai-python v1.2.3 (latest)
  • Package URL : https://github.com/openai/openai-python
  • Package Dependencies that needs to be resolved first:
    "httpx>=0.23.0, <1",
    "pydantic>=1.9.0, <3",
    "typing-extensions>=4.5, <5",
    "anyio>=3.5.0, <4",
    "distro>=1.7.0, <2",
    "tqdm > 4"
@RobertCraigie
Copy link

Does the library work if you use the patcher like you do in the raw request example?

import pyodide_http
pyodide_http.patch_all()

@psymbio
Copy link
Author

psymbio commented Nov 14, 2023

No,

import micropip
import pyodide_http
pyodide_http.patch_all()
await micropip.install('openai', keep_going=True)
await micropip.install("ssl")
import openai
from openai import OpenAI

client = OpenAI(
    api_key="API KEY",
)

chat_completion = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": "Say this is a test",
        }
    ],
    model="gpt-3.5-turbo",
)

Gives the same: APIConnectionError: Connection error. (The really long error message)

@RobertCraigie
Copy link

You may be interested in this issue: koenvo/pyodide-http#37

That mentions that it might be fairly simple to add support for httpx.

@RobertCraigie
Copy link

RobertCraigie commented Nov 14, 2023

I'm not very familiar with this aspect of httpx but it might not even require monkey patching, you might be able to pass in a custom transport.

@psymbio
Copy link
Author

psymbio commented Nov 14, 2023

Is it possible to schedule a meeting with you guys - I'm not sure if I completely understand how easy it is to implement this, or the resources required to do something like this.

@ryanking13
Copy link
Member

I get the BadZipFile error for this.

Could you check the url passed into micropip.install does not return 404 error or CORS error?

Should I consider rolling back to v0.28.0 which uses requests?

This would be the feasible option for now. Or you may consider adding httpx support in pyodide-http as the above comment.

@psymbio
Copy link
Author

psymbio commented Nov 14, 2023

For the CORS error, I follow these steps:

  1. Enable https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf a Chrome extension
  2. Pin the extension and watch the YouTube tutorial: https://www.youtube.com/watch?v=KruSUqLdxQA&ab_channel=eccoremproject
  3. You have to Toggle the extension to be on.
  4. Go to settings by clicking on the extension and then "Open options page"
  5. Here under Access-Control-Allow-Origin - select "*" instead of "ORIGIN".
  6. Now, click on the extension and "Reload the active tab" to use the extension.

So, no - I don't get the CORS error. I don't get the 404 error - the link works.

The only reason the rollback option fails right now is because of the BadZipFile error - can you check if you get the same?

@psymbio
Copy link
Author

psymbio commented Nov 14, 2023

For httpx - because the comment states nothing other than https://sans-io.readthedocs.io/ - could you direct me to a process that enables me to support it?

How do I go about doing it?

@RobertCraigie
Copy link

httpx is written with custom IO in mind so there isn't anything specific you need to do to "enable" it, you can directly override the default transport layer with your your own custom transport that forwards the request to pyodide's fetch instead.

here's some docs you might find helpful: https://www.python-httpx.org/advanced/#custom-transports

for the actual implementation internals, they would likely look very similar to the existing requests patch, https://github.com/koenvo/pyodide-http/blob/main/pyodide_http/_requests.py

@psymbio
Copy link
Author

psymbio commented Nov 14, 2023

Also, what is the reason for the BadZipFile error?

Sometimes wheels install after uploading it, other times when they are uploaded to Github and downloaded. But in this case, they both fail - ideally, I would have expected v0.28.1 to work without this error.

@ryanking13
Copy link
Member

Also, what is the reason for the BadZipFile error?

I guess the URL you used https://github.com/psymbio/tiktoken_rust_wasm/blob/main/packages/openai_wheel/openai-0.28.1-py3-none-any.whl returns HTML instead of wheel.

Could you try this URL instead

https://raw.githubusercontent.com/psymbio/tiktoken_rust_wasm/main/packages/openai_wheel/openai-0.28.1-py3-none-any.whl

Note that this URL sets CORS headers, so you don't need to use the CORS extension.

@psymbio
Copy link
Author

psymbio commented Nov 14, 2023

Thanks, this works - it now fails for the multidict library saying: ValueError: Can't find a pure Python 3 wheel for: 'multidict<5.0,>=4.5'.

Can this be resolved if I compile a wheel and install it before installing the openai library?

Just to understand another thing: uploading the wheel file to JupyterLite and installing it also results in the same BadZipFile error for me - this shouldn't be the case right?

@ryanking13
Copy link
Member

Can this be resolved if I compile a wheel and install it before installing the openai library?

Yes.

Just to understand another thing: uploading the wheel file to JupyterLite and installing it also results in the same BadZipFile error for me - this shouldn't be the case right?

That's a separate case, but the cause is probably the same.
Either the wheel file you uploaded isn't actually a wheel file (try running file command on the file), or the URL you used to install it from jupyterlite is returning something other than a wheel file (HTML, JSON, etc).

@psymbio
Copy link
Author

psymbio commented Nov 16, 2023

After resolving the multidict issue; I am currently stuck because of this: aio-libs/aiohttp#7803

@psymbio
Copy link
Author

psymbio commented Nov 17, 2023

Currently, I have resolved all the issue with other dependencies and such - but there is an issue with requesting - max retries exceeded: APIConnectionError: Error communicating with OpenAI: HTTPSConnectionPool(host='api.openai.com', port=443): Max retries exceeded with url: /v1/chat/completions (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x2b9c368>: Failed to establish a new connection: [Errno 50] Protocol not available')).

Code for recreation on JupyterLite:

import micropip
await micropip.install('https://raw.githubusercontent.com/psymbio/tiktoken_rust_wasm/main/packages/multidict/multidict-4.7.6-py3-none-any.whl', keep_going=True)
await micropip.install('https://raw.githubusercontent.com/psymbio/tiktoken_rust_wasm/main/packages/frozenlist/frozenlist-1.4.0-py3-none-any.whl', keep_going=True)
await micropip.install('https://raw.githubusercontent.com/psymbio/tiktoken_rust_wasm/main/packages/aiohttp/aiohttp-4.0.0a2.dev0-py3-none-any.whl', keep_going=True)
await micropip.install('https://raw.githubusercontent.com/psymbio/tiktoken_rust_wasm/main/packages/openai_wheel/openai-0.28.1-py3-none-any.whl', keep_going=True)
await micropip.install("ssl")
import ssl

import pyodide_http
pyodide_http.patch_all()

import openai
openai.api_key = "API KEY"
chat_completion = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello world"}])
Error:
    OSError                                   Traceback (most recent call last)
File /lib/python3.11/site-packages/urllib3/connection.py:203, in HTTPConnection._new_conn(self)
    202 try:
--> 203     sock = connection.create_connection(
    204         (self._dns_host, self.port),
    205         self.timeout,
    206         source_address=self.source_address,
    207         socket_options=self.socket_options,
    208     )
    209 except socket.gaierror as e:

File /lib/python3.11/site-packages/urllib3/util/connection.py:85, in create_connection(address, timeout, source_address, socket_options)
    84 try:
---> 85     raise err
    86 finally:
    87     # Break explicitly a reference cycle

File /lib/python3.11/site-packages/urllib3/util/connection.py:67, in create_connection(address, timeout, source_address, socket_options)
    66 # If provided, set socket level options before connecting.
---> 67 _set_socket_options(sock, socket_options)
    69 if timeout is not _DEFAULT_TIMEOUT:

File /lib/python3.11/site-packages/urllib3/util/connection.py:100, in _set_socket_options(sock, options)
    99 for opt in options:
--> 100     sock.setsockopt(*opt)

OSError: [Errno 50] Protocol not available

The above exception was the direct cause of the following exception:

NewConnectionError                        Traceback (most recent call last)
File /lib/python3.11/site-packages/urllib3/connectionpool.py:790, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
    789 # Make the request on the HTTPConnection object
--> 790 response = self._make_request(
    791     conn,
    792     method,
    793     url,
    794     timeout=timeout_obj,
    795     body=body,
    796     headers=headers,
    797     chunked=chunked,
    798     retries=retries,
    799     response_conn=response_conn,
    800     preload_content=preload_content,
    801     decode_content=decode_content,
    802     **response_kw,
    803 )
    805 # Everything went great!

File /lib/python3.11/site-packages/urllib3/connectionpool.py:491, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
    490         new_e = _wrap_proxy_error(new_e, conn.proxy.scheme)
--> 491     raise new_e
    493 # conn.request() calls http.client.*.request, not the method in
    494 # urllib3.request. It also calls makefile (recv) on the socket.

File /lib/python3.11/site-packages/urllib3/connectionpool.py:467, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
    466 try:
--> 467     self._validate_conn(conn)
    468 except (SocketTimeout, BaseSSLError) as e:

File /lib/python3.11/site-packages/urllib3/connectionpool.py:1096, in HTTPSConnectionPool._validate_conn(self, conn)
1095 if conn.is_closed:
-> 1096     conn.connect()
1098 if not conn.is_verified:

File /lib/python3.11/site-packages/urllib3/connection.py:611, in HTTPSConnection.connect(self)
    610 sock: socket.socket | ssl.SSLSocket
--> 611 self.sock = sock = self._new_conn()
    612 server_hostname: str = self.host

File /lib/python3.11/site-packages/urllib3/connection.py:218, in HTTPConnection._new_conn(self)
    217 except OSError as e:
--> 218     raise NewConnectionError(
    219         self, f"Failed to establish a new connection: {e}"
    220     ) from e
    222 # Audit hooks are only available in Python 3.8+

NewConnectionError: <urllib3.connection.HTTPSConnection object at 0x2b9c368>: Failed to establish a new connection: [Errno 50] Protocol not available

The above exception was the direct cause of the following exception:

MaxRetryError                             Traceback (most recent call last)
File /lib/python3.11/site-packages/requests/adapters.py:486, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    485 try:
--> 486     resp = conn.urlopen(
    487         method=request.method,
    488         url=url,
    489         body=request.body,
    490         headers=request.headers,
    491         redirect=False,
    492         assert_same_host=False,
    493         preload_content=False,
    494         decode_content=False,
    495         retries=self.max_retries,
    496         timeout=timeout,
    497         chunked=chunked,
    498     )
    500 except (ProtocolError, OSError) as err:

File /lib/python3.11/site-packages/urllib3/connectionpool.py:874, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
    871     log.warning(
    872         "Retrying (%r) after connection broken by '%r': %s", retries, err, url
    873     )
--> 874     return self.urlopen(
    875         method,
    876         url,
    877         body,
    878         headers,
    879         retries,
    880         redirect,
    881         assert_same_host,
    882         timeout=timeout,
    883         pool_timeout=pool_timeout,
    884         release_conn=release_conn,
    885         chunked=chunked,
    886         body_pos=body_pos,
    887         preload_content=preload_content,
    888         decode_content=decode_content,
    889         **response_kw,
    890     )
    892 # Handle redirect?

File /lib/python3.11/site-packages/urllib3/connectionpool.py:874, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
    871     log.warning(
    872         "Retrying (%r) after connection broken by '%r': %s", retries, err, url
    873     )
--> 874     return self.urlopen(
    875         method,
    876         url,
    877         body,
    878         headers,
    879         retries,
    880         redirect,
    881         assert_same_host,
    882         timeout=timeout,
    883         pool_timeout=pool_timeout,
    884         release_conn=release_conn,
    885         chunked=chunked,
    886         body_pos=body_pos,
    887         preload_content=preload_content,
    888         decode_content=decode_content,
    889         **response_kw,
    890     )
    892 # Handle redirect?

File /lib/python3.11/site-packages/urllib3/connectionpool.py:844, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
    842     new_e = ProtocolError("Connection aborted.", new_e)
--> 844 retries = retries.increment(
    845     method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
    846 )
    847 retries.sleep()

File /lib/python3.11/site-packages/urllib3/util/retry.py:515, in Retry.increment(self, method, url, response, error, _pool, _stacktrace)
    514     reason = error or ResponseError(cause)
--> 515     raise MaxRetryError(_pool, url, reason) from reason  # type: ignore[arg-type]
    517 log.debug("Incremented Retry for (url='%s'): %r", url, new_retry)

MaxRetryError: HTTPSConnectionPool(host='api.openai.com', port=443): Max retries exceeded with url: /v1/chat/completions (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x2b9c368>: Failed to establish a new connection: [Errno 50] Protocol not available'))

During handling of the above exception, another exception occurred:

ConnectionError                           Traceback (most recent call last)
File /lib/python3.11/site-packages/openai/api_requestor.py:606, in APIRequestor.request_raw(self, method, url, params, supplied_headers, files, stream, request_id, request_timeout)
    605 try:
--> 606     result = _thread_context.session.request(
    607         method,
    608         abs_url,
    609         headers=headers,
    610         data=data,
    611         files=files,
    612         stream=stream,
    613         timeout=request_timeout if request_timeout else TIMEOUT_SECS,
    614         proxies=_thread_context.session.proxies,
    615     )
    616 except requests.exceptions.Timeout as e:

File /lib/python3.11/site-packages/requests/sessions.py:589, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
    588 send_kwargs.update(settings)
--> 589 resp = self.send(prep, **send_kwargs)
    591 return resp

File /lib/python3.11/site-packages/requests/sessions.py:703, in Session.send(self, request, **kwargs)
    702 # Send the request
--> 703 r = adapter.send(request, **kwargs)
    705 # Total elapsed time of the request (approximately)

File /lib/python3.11/site-packages/requests/adapters.py:519, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    517         raise SSLError(e, request=request)
--> 519     raise ConnectionError(e, request=request)
    521 except ClosedPoolError as e:

ConnectionError: HTTPSConnectionPool(host='api.openai.com', port=443): Max retries exceeded with url: /v1/chat/completions (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x2b9c368>: Failed to establish a new connection: [Errno 50] Protocol not available'))

The above exception was the direct cause of the following exception:

APIConnectionError                        Traceback (most recent call last)
Cell In[2], line 9
    7 import openai
    8 openai.api_key = "API KEY"
----> 9 chat_completion = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello world"}])

File /lib/python3.11/site-packages/openai/api_resources/chat_completion.py:25, in ChatCompletion.create(cls, *args, **kwargs)
    23 while True:
    24     try:
---> 25         return super().create(*args, **kwargs)
    26     except TryAgain as e:
    27         if timeout is not None and time.time() > start + timeout:

File /lib/python3.11/site-packages/openai/api_resources/abstract/engine_api_resource.py:155, in EngineAPIResource.create(cls, api_key, api_base, api_type, request_id, api_version, organization, **params)
    129 @classmethod
    130 def create(
    131     cls,
(...)
    138     **params,
    139 ):
    140     (
    141         deployment_id,
    142         engine,
(...)
    152         api_key, api_base, api_type, api_version, organization, **params
    153     )
--> 155     response, _, api_key = requestor.request(
    156         "post",
    157         url,
    158         params=params,
    159         headers=headers,
    160         stream=stream,
    161         request_id=request_id,
    162         request_timeout=request_timeout,
    163     )
    165     if stream:
    166         # must be an iterator
    167         assert not isinstance(response, OpenAIResponse)

File /lib/python3.11/site-packages/openai/api_requestor.py:289, in APIRequestor.request(self, method, url, params, headers, files, stream, request_id, request_timeout)
    278 def request(
    279     self,
    280     method,
(...)
    287     request_timeout: Optional[Union[float, Tuple[float, float]]] = None,
    288 ) -> Tuple[Union[OpenAIResponse, Iterator[OpenAIResponse]], bool, str]:
--> 289     result = self.request_raw(
    290         method.lower(),
    291         url,
    292         params=params,
    293         supplied_headers=headers,
    294         files=files,
    295         stream=stream,
    296         request_id=request_id,
    297         request_timeout=request_timeout,
    298     )
    299     resp, got_stream = self._interpret_response(result, stream)
    300     return resp, got_stream, self.api_key

File /lib/python3.11/site-packages/openai/api_requestor.py:619, in APIRequestor.request_raw(self, method, url, params, supplied_headers, files, stream, request_id, request_timeout)
    617     raise error.Timeout("Request timed out: {}".format(e)) from e
    618 except requests.exceptions.RequestException as e:
--> 619     raise error.APIConnectionError(
    620         "Error communicating with OpenAI: {}".format(e)
    621     ) from e
    622 util.log_debug(
    623     "OpenAI API response",
    624     path=abs_url,
(...)
    627     request_id=result.headers.get("X-Request-Id"),
    628 )
    629 # Don't read the whole stream for debug logging unless necessary.

APIConnectionError: Error communicating with OpenAI: HTTPSConnectionPool(host='api.openai.com', port=443): Max retries exceeded with url: /v1/chat/completions (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x2b9c368>: Failed to establish a new connection: [Errno 50] Protocol not available'))

@psymbio
Copy link
Author

psymbio commented Nov 23, 2023

Is there any update on the:
APIConnectionError: Error communicating with OpenAI: HTTPSConnectionPool(host='api.openai.com', port=443): Max retries exceeded with url: /v1/chat/completions (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x2b9c368>: Failed to establish a new connection: [Errno 50] Protocol not available'))

issue?

@ryanking13 @RobertCraigie

@ryanking13
Copy link
Member

ryanking13 commented Nov 23, 2023

@psymbio No, I am not working on this issue currently. Did you try pyodide-http? The error message seems like it is using requests so pyodide-http might work.

@psymbio
Copy link
Author

psymbio commented Nov 23, 2023

Yes, I have patched pyodide-http in the above code - but it gives this error. For the newer version of the openai library (that uses httpx), I'm working on building the custom transport layer as well.

@psymbio
Copy link
Author

psymbio commented Dec 8, 2023

Updates:

Currently there's some work going on to support urllib3 on Pyodide which will be really helpful. It's in testing phase so, I've added test cases for the PR on urllib3 urllib3/urllib3#3195.

In the new release of the library OpenAI has implemented "configuring http clients" (https://github.com/openai/openai-python/blob/main/README.md#configuring-the-http-client).

So essentially, using the fix for the Transport API (see: https://www.python-httpx.org/advanced/#custom-transports, https://gist.github.com/florimondmanca/d56764d78d748eb9f73165da388e546e (copy paste this in a python file), and urllib3/urllib3#2073) the working code would look something like:

import micropip
await micropip.install('https://raw.githubusercontent.com/psymbio/tiktoken_rust_wasm/main/packages/multidict/multidict-4.7.6-py3-none-any.whl', keep_going=True)
await micropip.install('https://raw.githubusercontent.com/psymbio/tiktoken_rust_wasm/main/packages/frozenlist/frozenlist-1.4.0-py3-none-any.whl', keep_going=True)
await micropip.install('https://raw.githubusercontent.com/psymbio/tiktoken_rust_wasm/main/packages/aiohttp/aiohttp-4.0.0a2.dev0-py3-none-any.whl', keep_going=True)
await micropip.install('https://raw.githubusercontent.com/psymbio/tiktoken_rust_wasm/main/packages/openai_wheel/openai-1.3.7-py3-none-any.whl', keep_going=True)
await micropip.install('https://raw.githubusercontent.com/psymbio/tiktoken_rust_wasm/main/packages/urllib3/urllib3-2.1.0-py3-none-any.whl', keep_going=True)

# although here httpcore needs to be in the newest version and https://gist.github.com/florimondmanca/d56764d78d748eb9f73165da388e546e needs to be updated
await micropip.install('https://raw.githubusercontent.com/psymbio/tiktoken_rust_wasm/main/packages/httpcore/httpcore-0.12.3-py3-none-any.whl', keep_going=True)

await micropip.install("ssl")
import ssl

import pyodide_http
pyodide_http.patch_all()
import openai

import httpx
from openai import OpenAI

import urllib3
from urllib3_transport import URLLib3Transport

urllib3_transport_client = httpx.Client(transport=URLLib3Transport())
client = OpenAI(
    base_url="https://api.openai.com/v1/completions",
    api_key="xxx",
    http_client=urllib3_trasport_client
)

But the problem is that the transport support was for httpcore 0.12.x and now httpcore is at 1.0.2; 0.12.x had the implmentation for httpcore.SyncHTTPTransport (this is used in https://gist.github.com/florimondmanca/d56764d78d748eb9f73165da388e546e) the new version doesn't have this.

Possible solutions:

  1. New Version:

  2. Rollback to old version

    • Created the wheel of the old version: await micropip.install('https://raw.githubusercontent.com/psymbio/tiktoken_rust_wasm/main/packages/httpcore/httpcore-0.12.3-py3-none-any.whl', keep_going=True)

    Testing code:

    import micropip
    await micropip.install("ssl")
    import ssl
    await micropip.install('https://raw.githubusercontent.com/psymbio/tiktoken_rust_wasm/main/packages/urllib3/urllib3-2.1.0-py3-none-any.whl', keep_going=True)
    await micropip.install('https://raw.githubusercontent.com/psymbio/tiktoken_rust_wasm/main/packages/httpcore/httpcore-0.12.3-py3-none-any.whl', keep_going=True)
    from urllib3_transport import URLLib3Transport
    await micropip.install("httpx")
    import httpx

    Now, we get the issue that: ValueError: Requested 'httpcore==1.*', but httpcore==0.12.3 is already installed; that means we can't rollback.

I'll probably mention this issue in other issues to keep track of the issue.

@psymbio
Copy link
Author

psymbio commented Dec 9, 2023

Updates:

Commented on gist and did the custom transport update: encode/httpx#2994

Closed previous ticket and opened a new ticket on the openai-python repo.

@psymbio
Copy link
Author

psymbio commented Dec 10, 2023

Solution:

import micropip
await micropip.install('https://raw.githubusercontent.com/psymbio/pyodide_wheels/main/multidict/multidict-4.7.6-py3-none-any.whl', keep_going=True)
await micropip.install('https://raw.githubusercontent.com/psymbio/pyodide_wheels/main/frozenlist/frozenlist-1.4.0-py3-none-any.whl', keep_going=True)
await micropip.install('https://raw.githubusercontent.com/psymbio/pyodide_wheels/main/aiohttp/aiohttp-4.0.0a2.dev0-py3-none-any.whl', keep_going=True)
await micropip.install('https://raw.githubusercontent.com/psymbio/pyodide_wheels/main/openai/openai-1.3.7-py3-none-any.whl', keep_going=True)
await micropip.install('https://raw.githubusercontent.com/psymbio/pyodide_wheels/main/urllib3/urllib3-2.1.0-py3-none-any.whl', keep_going=True)
await micropip.install("ssl")
import ssl
await micropip.install("httpx", keep_going=True)
import httpx
await micropip.install('https://raw.githubusercontent.com/psymbio/pyodide_wheels/main/urllib3/urllib3-2.1.0-py3-none-any.whl', keep_going=True)
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
import json

class URLLib3Transport(httpx.BaseTransport):
    def __init__(self):
        self.pool = urllib3.PoolManager()

    def handle_request(self, request: httpx.Request):
        payload = json.loads(request.content.decode("utf-8").replace("'",'"'))
        urllib3_response = self.pool.request(request.method, str(request.url), headers=request.headers, json=payload)  # Convert httpx.URL to string
        content = json.loads(urllib3_response.data.decode('utf-8'))  # Decode the data and load as JSON
        stream = httpx.ByteStream(json.dumps(content).encode("utf-8"))  # Convert back to JSON and encode
        headers = [(b"content-type", b"application/json")]
        return httpx.Response(200, headers=headers, stream=stream)

client = httpx.Client(transport=URLLib3Transport())
from openai import OpenAI

openai_client = OpenAI(
    base_url="https://api.openai.com/v1/",
    api_key="xxx",
    http_client=client
)
response = openai_client.chat.completions.with_raw_response.create(
    messages=[{
        "role": "user",
        "content": "sing me a song",
    }],
    model="gpt-3.5-turbo",
    max_tokens=30,
    temperature=0.7
)
completion = response.parse()
print(completion)

Output:

ChatCompletion(id='chatcmpl-8U6JZbqVEFJp5t9lh4jZrADf4dKP2', choices=[Choice(finish_reason='length', index=0, message=ChatCompletionMessage(content="(Verse 1)\nIn a world full of wonder, let's sing a melody,\nWhere love and joy intertwine, and hearts can be set", role='assistant', function_call=None, tool_calls=None))], created=1702184805, model='gpt-3.5-turbo-0613', object='chat.completion', system_fingerprint=None, usage=CompletionUsage(completion_tokens=30, prompt_tokens=11, total_tokens=41))

Thanks @RobertCraigie and @ryanking13!

@psymbio psymbio closed this as completed Dec 10, 2023
@RobertCraigie
Copy link

Amazing @psymbio! Thanks for sharing your solution :)

@epugh
Copy link

epugh commented Mar 9, 2024

I gave the example a try, and I get furthur then previously when I would fail at the:

ImportError: cannot import name 'Iterator' from 'typing_extensions' (/lib/python3.11/site-packages/typing_extensions.py)

Now I get down to:

ImportError: cannot import name 'is_list_type' from 'openai._utils._utils' (/lib/python3.11/site-packages/openai/_utils/_utils.py)

@hoodmane
Copy link
Member

hoodmane commented Mar 9, 2024

See also #4549, which would add openai to the package set.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants