From 6fe00ec29223ed5ca5220ef9d6fdca8c4ded7e5e Mon Sep 17 00:00:00 2001 From: dreamcode1994 <118388087+dreamcode1994@users.noreply.github.com> Date: Thu, 5 Oct 2023 19:53:10 +0530 Subject: [PATCH] Added Support of reaching openai Without Proxy --- README.md | 2 +- numexa/__init__.py | 2 +- numexa/api_resources/apis.py | 13 +- numexa/api_resources/base_client.py | 148 +++++++++++++++++------ numexa/api_resources/global_constants.py | 2 +- numexa/api_resources/utils.py | 1 + 6 files changed, 125 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 3d0d48b..2db6ea6 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ os.environ["NUMEXA_API_KEY"] = "NUMEXA_API_KEY" **Numexa Without proxy:** ```python import os -os.environ["NUMEXA_PROXY"] = "false" +os.environ["NUMEXA_PROXY"] = "disable" ``` **Virtual Keys:** Navigate to the "API Keys" page on [Numexa](https://app.numexa.io/admin/keys) and hit the "Generate" button. Choose your AI provider and assign a unique name to your key. Your virtual key is ready! diff --git a/numexa/__init__.py b/numexa/__init__.py index 65b3914..02e17ea 100644 --- a/numexa/__init__.py +++ b/numexa/__init__.py @@ -31,7 +31,7 @@ ) api_key = os.environ.get(NUMEXA_API_KEY) -if os.environ.get(NUMEXA_PROXY, "true").lower() == "true": +if not os.environ.get(NUMEXA_PROXY): base_url = NUMEXA_PROXY_URL else: base_url = NUMEXA_DIRECT_URL diff --git a/numexa/api_resources/apis.py b/numexa/api_resources/apis.py index 4c2cb07..1f93122 100644 --- a/numexa/api_resources/apis.py +++ b/numexa/api_resources/apis.py @@ -1,3 +1,4 @@ +import os from typing import Optional, Union, overload, Literal, List, Mapping, Any from numexa.api_resources.base_client import APIClient from .utils import ( @@ -215,9 +216,15 @@ def create( top_p=top_p, **kwargs, ) + # Proxy On + if not os.environ.get("NUMEXA_PROXY"): + url = "/chat/completions" + # proxy Off + else: + url = "/chat/completions/direct" if config.mode == Modes.SINGLE.value: return cls(_client)._post( - "/chat/completions", + url, body=config.llms, mode=Modes.SINGLE.value, params=params, @@ -227,7 +234,7 @@ def create( ) if config.mode == Modes.FALLBACK.value: return cls(_client)._post( - "/chat/completions", + url, body=config.llms, mode=Modes.FALLBACK, params=params, @@ -237,7 +244,7 @@ def create( ) if config.mode == Modes.AB_TEST.value: return cls(_client)._post( - "/v1/chatComplete", + url, body=config.llms, mode=Modes.AB_TEST, params=params, diff --git a/numexa/api_resources/base_client.py b/numexa/api_resources/base_client.py index be87bca..20d8ba8 100644 --- a/numexa/api_resources/base_client.py +++ b/numexa/api_resources/base_client.py @@ -145,6 +145,17 @@ def post( stream=stream, params=params, ) + elif path in NumexaApiPaths.CHAT_COMPLETION_DIRECT: + body = cast(List[Body], body) + opts = self._construct_direct( + method="post", + url=path.split("/direct")[0], + body=body, + mode=mode, + stream=stream, + params=params, + ) + elif path.endswith("/generate"): opts = self._construct_generate_options( method="post", @@ -205,6 +216,28 @@ def _construct( opts.headers = None return opts + def _construct_direct( + self, + *, + method: str, + url: str, + body: List[Body], + mode: str, + stream: bool, + params: Params, + ) -> Options: + opts = Options.construct() + opts.method = method + opts.url = url + params_dict = {} if params is None else params.dict() + json_body = { + "model": self._config_direct(mode, body), + "messages": params_dict.get("messages", [{}]) + } + opts.json_body = remove_empty_values(json_body) + opts.headers = None + return opts + def _config(self, mode: str, body: List[Body]) -> RequestConfig: config = RequestConfig(mode=mode, options=[]) for i in body: @@ -216,18 +249,32 @@ def _config(self, mode: str, body: List[Body]) -> RequestConfig: config.options.append(options) return config + def _config_direct(self, mode: str, body: List[Body]) -> List[str]: + config = [] + for i in body: + config.append(i.model) + return config + @property def _default_headers(self) -> Mapping[str, str]: - return { - "Content-Type": "application/json", - f"{NUMEXA_HEADER_PREFIX}Api-Key": self.api_key, - f"{NUMEXA_HEADER_PREFIX}package-version": f"numexa-{VERSION}", - f"{NUMEXA_HEADER_PREFIX}runtime": platform.python_implementation(), - f"{NUMEXA_HEADER_PREFIX}runtime-version": platform.python_version(), - f"{NUMEXA_HEADER_PREFIX}Cache": "true", - "Authorization": os.environ.get(OPEN_API_KEY), - - } + # Proxy ON + if not os.environ.get("NUMEXA_PROXY"): + return { + "Content-Type": "application/json", + f"{NUMEXA_HEADER_PREFIX}Api-Key": self.api_key, + f"{NUMEXA_HEADER_PREFIX}package-version": f"numexa-{VERSION}", + f"{NUMEXA_HEADER_PREFIX}runtime": platform.python_implementation(), + f"{NUMEXA_HEADER_PREFIX}runtime-version": platform.python_version(), + f"{NUMEXA_HEADER_PREFIX}Cache": "true", + "Authorization": os.environ.get(OPEN_API_KEY), + + } + # Proxy Off + else: + return { + "Content-Type": "application/json", + "Authorization": os.environ.get(OPEN_API_KEY) + } def _build_headers(self, options: Options) -> httpx.Headers: custom_headers = options.headers or {} @@ -266,7 +313,7 @@ def __exit__( ) -> None: self.close() - def _build_request(self, options: Options) -> httpx.Request: + def _build_request(self, options: Options) -> List[httpx.Request]: headers = self._build_headers(options) params = options.params json_body = options.json_body @@ -278,7 +325,28 @@ def _build_request(self, options: Options) -> httpx.Request: json=json_body, timeout=options.timeout, ) - return request + return [request] + + def _build_request_direct(self, options: Options) -> List[httpx.Request]: + headers = self._build_headers(options) + new_payload = dict() + request_list = [] + params = options.params + json_body = options.json_body + messages = json_body.get("messages", [{}]) + models = json_body.get("model", [""]) + new_payload["messages"] = messages + for model in models: + new_payload["model"] = model + request_list.append(self._client.build_request( + method=options.method, + url=options.url, + headers=headers, + params=params, + json=new_payload, + timeout=options.timeout, + )) + return request_list @overload def _request( @@ -321,32 +389,38 @@ def _request( cast_to: Type[ResponseT], stream_cls: Type[StreamT], ) -> Union[ResponseT, StreamT]: - request = self._build_request(options) - try: - res = self._client.send(request, auth=self.custom_auth, stream=stream) - res.raise_for_status() - except httpx.HTTPStatusError as err: # 4xx and 5xx errors - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - err.response.read() - raise self._make_status_error_from_response(request, err.response) from None - except httpx.TimeoutException as err: - raise APITimeoutError(request=request) from err - except Exception as err: - raise APIConnectionError(request=request) from err - if stream or res.headers["content-type"] == "text/event-stream": - if stream_cls is None: - raise MissingStreamClassError() - stream_response = stream_cls( - response=res, cast_to=self._extract_stream_chunk_type(stream_cls) + # proxy on + if not os.environ.get("NUMEXA_PROXY"): + request_list = self._build_request(options) + # proxy off + else: + request_list = self._build_request_direct(options) + for request in request_list: + try: + res = self._client.send(request, auth=self.custom_auth, stream=stream) + res.raise_for_status() + except httpx.HTTPStatusError as err: # 4xx and 5xx errors + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + print(err.response.read()) + continue + # raise self._make_status_error_from_response(request, err.response) from None + except httpx.TimeoutException as err: + raise APITimeoutError(request=request) from err + except Exception as err: + raise APIConnectionError(request=request) from err + if stream or res.headers["content-type"] == "text/event-stream": + if stream_cls is None: + raise MissingStreamClassError() + stream_response = stream_cls( + response=res, cast_to=self._extract_stream_chunk_type(stream_cls) + ) + return stream_response + response = cast( + ResponseT, + cast_to(**res.json()), ) - return stream_response - - response = cast( - ResponseT, - cast_to(**res.json()), - ) - return response + return response def _extract_stream_chunk_type(self, stream_cls: Type) -> type: args = get_args(stream_cls) diff --git a/numexa/api_resources/global_constants.py b/numexa/api_resources/global_constants.py index 19d5b29..dda297b 100644 --- a/numexa/api_resources/global_constants.py +++ b/numexa/api_resources/global_constants.py @@ -28,7 +28,7 @@ VERSION = "0.1.0" DEFAULT_TIMEOUT = 60 NUMEXA_HEADER_PREFIX = "X-Numexa-" -NUMEXA_DIRECT_URL = "https://app.numexa.io/proxy/v1/openapi" +NUMEXA_DIRECT_URL = "https://api.openai.com/v1" NUMEXA_API_KEY = "NUMEXA_API_KEY" NUMEXA_PROXY_URL = "https://app.numexa.io/proxy/v1/openai" diff --git a/numexa/api_resources/utils.py b/numexa/api_resources/utils.py index 7597b37..8f28f88 100644 --- a/numexa/api_resources/utils.py +++ b/numexa/api_resources/utils.py @@ -99,6 +99,7 @@ class NumexaApiPaths(str, Enum, metaclass=MetaEnum): CHAT_COMPLETION = "/chat/completions" COMPLETION = "/complete" GENERATION = "/v1/prompts/{prompt_id}/generate" + CHAT_COMPLETION_DIRECT = "/chat/completions/direct" class Options(BaseModel):