Skip to content

Commit

Permalink
Bump version to 0.5.0: Merge pull request #10 from atomiechen/dev
Browse files Browse the repository at this point in the history
Bump version to 0.5.0
  • Loading branch information
atomiechen authored Jul 30, 2023
2 parents 63d7630 + 86f2d72 commit 3b2a12a
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 150 deletions.
37 changes: 18 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@ Example scripts are placed in [tests](./tests) folder.

## OpenAI API Request

### Endpoints

Each API request will connect to an endpoint along with some API configurations, which include: `api_key`, `organization`, `api_base`, `api_type` and `api_version`.

An `Endpoint` object contains these information. An `EndpointManager` acts like a list and can be used to rotate the next endpoint. See [test_endpoint.py](./tests/test_endpoint.py).

There are 5 methods for specifying endpoint info:

1. (each API call) Pass these fields as keyword parameters.
2. (each API call) Pass an `endpoint` keyword parameter to specify an `Endpoint`.
3. (each API call) Pass an `endpoint_manager` keyword parameter to specify an `EndpointManager`.
4. (global) Set class variables: `OpenAIAPI.api_base`, `OpenAIAPI.api_key`, `OpenAIAPI.organization`, `OpenAIAPI.api_type`, `OpenAIAPI.api_version`.
5. (global) Set environment variables: `OPENAI_API_KEY`, `OPENAI_ORGANIZATION`, `OPENAI_API_BASE`, `OPENAI_API_TYPE`, `OPENAI_API_VERSION`.

**Note**: If a field is set to `None` in the previous method, it will be replaced by the non-`None` value in the subsequent method, until a default value is used (OpenAI's endpoint information).

**Azure OpenAI APIs are supported:** Specify `api_type='azure'`, and set `api_base` and `api_key` accordingly. See [test_azure.py](./tests/test_azure.py). Please refer to [Azure OpenAI Service Documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/) for details.

### Logger

You can pass custom `logger` and `log_marks` (a string or a collection of strings) to `chat`/`completions` to get input and output logging.
Expand All @@ -50,17 +68,6 @@ response = OpenAIAPI.chat(
print(response['choices'][0]['message']['content'])
```

### Authorization

API key and organization will be loaded using the environment variable `OPENAI_API_KEY` and `OPENAI_ORGANIZATION`, or you can set manually:

```python
OpenAIAPI.api_key = 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
OpenAIAPI.organization = '......' # default: None
```

Or, you can pass `api_key` and `organization` parameters in each API call.

### Stream response

Stream response of `chat`/`completions`/`finetunes_list_events` can be achieved using `steam` parameter:
Expand Down Expand Up @@ -111,14 +118,6 @@ for text in OpenAIAPI.stream_chat(response):

Please refer to [OpenAI official API reference](https://platform.openai.com/docs/api-reference) for details.

### Azure

**Azure OpenAI APIs are supported!**

Refer to [test_azure.py](./tests/test_azure.py) for example usage.

Refer to [Azure OpenAI Service Documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/).



## Prompt
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "HandyLLM"
version = "0.4.0"
version = "0.5.0"
authors = [
{ name="Atomie CHEN", email="atomic_cwh@163.com" },
]
Expand Down
2 changes: 1 addition & 1 deletion src/handyllm/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .openai_api import OpenAIAPI
from .endpoint_manager import EndpointManager
from .endpoint_manager import Endpoint, EndpointManager
from .prompt_converter import PromptConverter
143 changes: 73 additions & 70 deletions src/handyllm/endpoint_manager.py
Original file line number Diff line number Diff line change
@@ -1,81 +1,84 @@
from threading import Lock
from . import OpenAIAPI
from collections.abc import MutableSequence


class Endpoint:
def __init__(
self,
name=None,
api_key=None,
organization=None,
api_base=None,
api_type=None,
api_version=None,
):
self.name = name if name else f"ep_{id(self)}"
self.api_key = api_key
self.organization = organization
self.api_base = api_base
self.api_type = api_type
self.api_version = api_version

def __str__(self) -> str:
# do not print api_key
listed_attributes = [
f'name={repr(self.name)}' if self.name else None,
f'api_key=*' if self.api_key else None,
f'organization={repr(self.organization)}' if self.organization else None,
f'api_base={repr(self.api_base)}' if self.api_base else None,
f'api_type={repr(self.api_type)}' if self.api_type else None,
f'api_version={repr(self.api_version)}' if self.api_version else None,
]
# remove None in listed_attributes
listed_attributes = [item for item in listed_attributes if item]
return f"Endpoint({', '.join(listed_attributes)})"

def get_api_info(self):
return (
self.api_key,
self.organization,
self.api_base,
self.api_type,
self.api_version
)


class EndpointManager(MutableSequence):

class EndpointManager:

def __init__(self):
self._lock = Lock()
self._last_idx_endpoint = 0
self._endpoints = []

def clear(self):
self._last_idx_endpoint = 0
self._endpoints.clear()

self._base_urls = []
self._last_idx_url = 0

self._keys = []
self._last_idx_key = 0

self._organizations = []
self._last_idx_organization = 0
def __len__(self) -> int:
return len(self._endpoints)

def add_base_url(self, base_url: str):
if isinstance(base_url, str) and base_url.strip() != '':
self._base_urls.append(base_url)
def __getitem__(self, idx: int) -> Endpoint:
return self._endpoints[idx]

def add_key(self, key: str):
if isinstance(key, str) and key.strip() != '':
self._keys.append(key)

def add_organization(self, organization: str):
if isinstance(organization, str) and organization.strip() != '':
self._organizations.append(organization)

def set_base_urls(self, base_urls):
self._base_urls = [url for url in base_urls if isinstance(url, str) and url.strip() != '']

def set_keys(self, keys):
self._keys = [key for key in keys if isinstance(key, str) and key.strip() != '']

def set_organizations(self, organizations):
self._organizations = [organization for organization in organizations if isinstance(organization, str) and organization.strip() != '']
def __setitem__(self, idx: int, endpoint: Endpoint):
self._endpoints[idx] = endpoint

def get_base_url(self):
if len(self._base_urls) == 0:
return OpenAIAPI.get_api_base()
else:
base_url = self._base_urls[self._last_idx_url]
if self._last_idx_url == len(self._base_urls) - 1:
self._last_idx_url = 0
else:
self._last_idx_url += 1
return base_url
def __delitem__(self, idx: int):
del self._endpoints[idx]

def get_key(self):
if len(self._keys) == 0:
return OpenAIAPI.get_api_key()
else:
key = self._keys[self._last_idx_key]
if self._last_idx_key == len(self._keys) - 1:
self._last_idx_key = 0
else:
self._last_idx_key += 1
return key

def get_organization(self):
if len(self._organizations) == 0:
return OpenAIAPI.get_organization()
else:
organization = self._organizations[self._last_idx_organization]
if self._last_idx_organization == len(self._keys) - 1:
self._last_idx_organization = 0
else:
self._last_idx_organization += 1
return organization

def get_endpoint(self):
def insert(self, idx: int, endpoint: Endpoint):
self._endpoints.insert(idx, endpoint)

def add_endpoint_by_info(self, **kwargs):
endpoint = Endpoint(**kwargs)
self.append(endpoint)

def get_next_endpoint(self) -> Endpoint:
with self._lock:
# compose full url
base_url = self.get_base_url()
# get API key
api_key = self.get_key()
# get organization
organization = self.get_organization()
return base_url, api_key, organization
endpoint = self._endpoints[self._last_idx_endpoint]
if self._last_idx_endpoint == len(self._endpoints) - 1:
self._last_idx_endpoint = 0
else:
self._last_idx_endpoint += 1
return endpoint

Loading

0 comments on commit 3b2a12a

Please sign in to comment.