CoolFace
Apppublic

aphilippov/python-server-api

sourceHugging Faceapache-2.0updated 3y agoView on Hugging Face
0likes
openai_utils.py615 linesDownload Raw Back to oai
1import json2import logging3import os4import tempfile5from pathlib import Path6from typing import Any, Dict, List, Optional, Set, Union7 8from dotenv import find_dotenv, load_dotenv9 10try:11    from openai import OpenAI12    from openai.types.beta.assistant import Assistant13 14    ERROR = None15except ImportError:16    ERROR = ImportError("Please install openai>=1 to use autogen.OpenAIWrapper.")17    OpenAI = object18    Assistant = object19 20NON_CACHE_KEY = ["api_key", "base_url", "api_type", "api_version"]21DEFAULT_AZURE_API_VERSION = "2023-08-01-preview"22OAI_PRICE1K = {23    "text-ada-001": 0.0004,24    "text-babbage-001": 0.0005,25    "text-curie-001": 0.002,26    "code-cushman-001": 0.024,27    "code-davinci-002": 0.1,28    "text-davinci-002": 0.02,29    "text-davinci-003": 0.02,30    "gpt-3.5-turbo-instruct": (0.0015, 0.002),31    "gpt-3.5-turbo-0301": (0.0015, 0.002),  # deprecate in Sep32    "gpt-3.5-turbo-0613": (0.0015, 0.002),33    "gpt-3.5-turbo-16k": (0.003, 0.004),34    "gpt-3.5-turbo-16k-0613": (0.003, 0.004),35    "gpt-35-turbo": (0.0015, 0.002),36    "gpt-35-turbo-16k": (0.003, 0.004),37    "gpt-35-turbo-instruct": (0.0015, 0.002),38    "gpt-4": (0.03, 0.06),39    "gpt-4-32k": (0.06, 0.12),40    "gpt-4-0314": (0.03, 0.06),  # deprecate in Sep41    "gpt-4-32k-0314": (0.06, 0.12),  # deprecate in Sep42    "gpt-4-0613": (0.03, 0.06),43    "gpt-4-32k-0613": (0.06, 0.12),44    # 11-0645    "gpt-3.5-turbo": (0.001, 0.002),46    "gpt-3.5-turbo-1106": (0.001, 0.002),47    "gpt-35-turbo-1106": (0.001, 0.002),48    "gpt-4-1106-preview": (0.01, 0.03),49    "gpt-4-1106-vision-preview": (0.01, 0.03),  # TODO: support vision pricing of images50}51 52 53def get_key(config: Dict[str, Any]) -> str:54    """Get a unique identifier of a configuration.55 56    Args:57        config (dict or list): A configuration.58 59    Returns:60        tuple: A unique identifier which can be used as a key for a dict.61    """62    copied = False63    for key in NON_CACHE_KEY:64        if key in config:65            config, copied = config.copy() if not copied else config, True66            config.pop(key)67    # if isinstance(config, dict):68    #     return tuple(get_key(x) for x in sorted(config.items()))69    # if isinstance(config, list):70    #     return tuple(get_key(x) for x in config)71    # return config72    return json.dumps(config, sort_keys=True)73 74 75def get_config_list(76    api_keys: List, base_urls: Optional[List] = None, api_type: Optional[str] = None, api_version: Optional[str] = None77) -> List[Dict]:78    """Get a list of configs for OpenAI API client.79 80    Args:81        api_keys (list): The api keys for openai api calls.82        base_urls (list, optional): The api bases for openai api calls. If provided, should match the length of api_keys.83        api_type (str, optional): The api type for openai api calls.84        api_version (str, optional): The api version for openai api calls.85 86    Returns:87        list: A list of configs for OepnAI API calls.88 89    Example:90    ```91    # Define a list of API keys92    api_keys = ['key1', 'key2', 'key3']93 94    # Optionally, define a list of base URLs corresponding to each API key95    base_urls = ['https://api.service1.com', 'https://api.service2.com', 'https://api.service3.com']96 97    # Optionally, define the API type and version if they are common for all keys98    api_type = 'azure'99    api_version = '2023-08-01-preview'100 101    # Call the get_config_list function to get a list of configuration dictionaries102    config_list = get_config_list(api_keys, base_urls, api_type, api_version)103    ```104 105    """106    if base_urls is not None:107        assert len(api_keys) == len(base_urls), "The length of api_keys must match the length of base_urls"108    config_list = []109    for i, api_key in enumerate(api_keys):110        if not api_key.strip():111            continue112        config = {"api_key": api_key}113        if base_urls:114            config["base_url"] = base_urls[i]115        if api_type:116            config["api_type"] = api_type117        if api_version:118            config["api_version"] = api_version119        config_list.append(config)120    return config_list121 122 123def config_list_openai_aoai(124    key_file_path: Optional[str] = ".",125    openai_api_key_file: Optional[str] = "key_openai.txt",126    aoai_api_key_file: Optional[str] = "key_aoai.txt",127    openai_api_base_file: Optional[str] = "base_openai.txt",128    aoai_api_base_file: Optional[str] = "base_aoai.txt",129    exclude: Optional[str] = None,130) -> List[Dict]:131    """Get a list of configs for OpenAI API client (including Azure or local model deployments that support OpenAI's chat completion API).132 133    This function constructs configurations by reading API keys and base URLs from environment variables or text files.134    It supports configurations for both OpenAI and Azure OpenAI services, allowing for the exclusion of one or the other.135    When text files are used, the environment variables will be overwritten.136    To prevent text files from being used, set the corresponding file name to None.137    Or set key_file_path to None to disallow reading from text files.138 139    Args:140        key_file_path (str, optional): The directory path where the API key files are located. Defaults to the current directory.141        openai_api_key_file (str, optional): The filename containing the OpenAI API key. Defaults to 'key_openai.txt'.142        aoai_api_key_file (str, optional): The filename containing the Azure OpenAI API key. Defaults to 'key_aoai.txt'.143        openai_api_base_file (str, optional): The filename containing the OpenAI API base URL. Defaults to 'base_openai.txt'.144        aoai_api_base_file (str, optional): The filename containing the Azure OpenAI API base URL. Defaults to 'base_aoai.txt'.145        exclude (str, optional): The API type to exclude from the configuration list. Can be 'openai' or 'aoai'. Defaults to None.146 147    Returns:148        List[Dict]: A list of configuration dictionaries. Each dictionary contains keys for 'api_key',149            and optionally 'base_url', 'api_type', and 'api_version'.150 151    Raises:152        FileNotFoundError: If the specified key files are not found and the corresponding API key is not set in the environment variables.153 154    Example:155        # To generate configurations excluding Azure OpenAI:156        configs = config_list_openai_aoai(exclude='aoai')157 158    File samples:159        - key_aoai.txt160 161        ```162        aoai-12345abcdef67890ghijklmnopqr163        aoai-09876zyxwvuts54321fedcba164        ```165 166        - base_aoai.txt167 168        ```169        https://api.azure.com/v1170        https://api.azure2.com/v1171        ```172 173    Notes:174        - The function checks for API keys and base URLs in the following environment variables: 'OPENAI_API_KEY', 'AZURE_OPENAI_API_KEY',175          'OPENAI_API_BASE' and 'AZURE_OPENAI_API_BASE'. If these are not found, it attempts to read from the specified files in the176          'key_file_path' directory.177        - The API version for Azure configurations is set to DEFAULT_AZURE_API_VERSION by default.178        - If 'exclude' is set to 'openai', only Azure OpenAI configurations are returned, and vice versa.179        - The function assumes that the API keys and base URLs in the environment variables are separated by new lines if there are180          multiple entries.181    """182    if exclude != "openai" and key_file_path is not None:183        # skip if key_file_path is None184        if openai_api_key_file is not None:185            # skip if openai_api_key_file is None186            try:187                with open(f"{key_file_path}/{openai_api_key_file}") as key_file:188                    os.environ["OPENAI_API_KEY"] = key_file.read().strip()189            except FileNotFoundError:190                logging.info(191                    "OPENAI_API_KEY is not found in os.environ "192                    "and key_openai.txt is not found in the specified path. You can specify the api_key in the config_list."193                )194        if openai_api_base_file is not None:195            # skip if openai_api_base_file is None196            try:197                with open(f"{key_file_path}/{openai_api_base_file}") as key_file:198                    os.environ["OPENAI_API_BASE"] = key_file.read().strip()199            except FileNotFoundError:200                logging.info(201                    "OPENAI_API_BASE is not found in os.environ "202                    "and base_openai.txt is not found in the specified path. You can specify the base_url in the config_list."203                )204    if exclude != "aoai" and key_file_path is not None:205        # skip if key_file_path is None206        if aoai_api_key_file is not None:207            try:208                with open(f"{key_file_path}/{aoai_api_key_file}") as key_file:209                    os.environ["AZURE_OPENAI_API_KEY"] = key_file.read().strip()210            except FileNotFoundError:211                logging.info(212                    "AZURE_OPENAI_API_KEY is not found in os.environ "213                    "and key_aoai.txt is not found in the specified path. You can specify the api_key in the config_list."214                )215        if aoai_api_base_file is not None:216            try:217                with open(f"{key_file_path}/{aoai_api_base_file}") as key_file:218                    os.environ["AZURE_OPENAI_API_BASE"] = key_file.read().strip()219            except FileNotFoundError:220                logging.info(221                    "AZURE_OPENAI_API_BASE is not found in os.environ "222                    "and base_aoai.txt is not found in the specified path. You can specify the base_url in the config_list."223                )224    aoai_config = (225        get_config_list(226            # Assuming Azure OpenAI api keys in os.environ["AZURE_OPENAI_API_KEY"], in separated lines227            api_keys=os.environ.get("AZURE_OPENAI_API_KEY", "").split("\n"),228            # Assuming Azure OpenAI api bases in os.environ["AZURE_OPENAI_API_BASE"], in separated lines229            base_urls=os.environ.get("AZURE_OPENAI_API_BASE", "").split("\n"),230            api_type="azure",231            api_version=DEFAULT_AZURE_API_VERSION,232        )233        if exclude != "aoai"234        else []235    )236    # process openai base urls237    base_urls = os.environ.get("OPENAI_API_BASE", None)238    base_urls = base_urls if base_urls is None else base_urls.split("\n")239    openai_config = (240        get_config_list(241            # Assuming OpenAI API_KEY in os.environ["OPENAI_API_KEY"]242            api_keys=os.environ.get("OPENAI_API_KEY", "").split("\n"),243            base_urls=base_urls,244        )245        if exclude != "openai"246        else []247    )248    config_list = openai_config + aoai_config249    return config_list250 251 252def config_list_from_models(253    key_file_path: Optional[str] = ".",254    openai_api_key_file: Optional[str] = "key_openai.txt",255    aoai_api_key_file: Optional[str] = "key_aoai.txt",256    aoai_api_base_file: Optional[str] = "base_aoai.txt",257    exclude: Optional[str] = None,258    model_list: Optional[list] = None,259) -> List[Dict]:260    """261    Get a list of configs for API calls with models specified in the model list.262 263    This function extends `config_list_openai_aoai` by allowing to clone its' out for each of the models provided.264    Each configuration will have a 'model' key with the model name as its value. This is particularly useful when265    all endpoints have same set of models.266 267    Args:268        key_file_path (str, optional): The path to the key files.269        openai_api_key_file (str, optional): The file name of the OpenAI API key.270        aoai_api_key_file (str, optional): The file name of the Azure OpenAI API key.271        aoai_api_base_file (str, optional): The file name of the Azure OpenAI API base.272        exclude (str, optional): The API type to exclude, "openai" or "aoai".273        model_list (list, optional): The list of model names to include in the configs.274 275    Returns:276        list: A list of configs for OpenAI API calls, each including model information.277 278    Example:279    ```280        # Define the path where the API key files are located281        key_file_path = '/path/to/key/files'282 283        # Define the file names for the OpenAI and Azure OpenAI API keys and bases284        openai_api_key_file = 'key_openai.txt'285        aoai_api_key_file = 'key_aoai.txt'286        aoai_api_base_file = 'base_aoai.txt'287 288        # Define the list of models for which to create configurations289        model_list = ['gpt-4', 'gpt-3.5-turbo']290 291        # Call the function to get a list of configuration dictionaries292        config_list = config_list_from_models(293            key_file_path=key_file_path,294            openai_api_key_file=openai_api_key_file,295            aoai_api_key_file=aoai_api_key_file,296            aoai_api_base_file=aoai_api_base_file,297            model_list=model_list298        )299 300        # The `config_list` will contain configurations for the specified models, for example:301        # [302        #     {'api_key': '...', 'base_url': 'https://api.openai.com', 'model': 'gpt-4'},303        #     {'api_key': '...', 'base_url': 'https://api.openai.com', 'model': 'gpt-3.5-turbo'}304        # ]305    ```306    """307    config_list = config_list_openai_aoai(308        key_file_path,309        openai_api_key_file,310        aoai_api_key_file,311        aoai_api_base_file,312        exclude,313    )314    if model_list:315        config_list = [{**config, "model": model} for model in model_list for config in config_list]316    return config_list317 318 319def config_list_gpt4_gpt35(320    key_file_path: Optional[str] = ".",321    openai_api_key_file: Optional[str] = "key_openai.txt",322    aoai_api_key_file: Optional[str] = "key_aoai.txt",323    aoai_api_base_file: Optional[str] = "base_aoai.txt",324    exclude: Optional[str] = None,325) -> List[Dict]:326    """Get a list of configs for 'gpt-4' followed by 'gpt-3.5-turbo' API calls.327 328    Args:329        key_file_path (str, optional): The path to the key files.330        openai_api_key_file (str, optional): The file name of the openai api key.331        aoai_api_key_file (str, optional): The file name of the azure openai api key.332        aoai_api_base_file (str, optional): The file name of the azure openai api base.333        exclude (str, optional): The api type to exclude, "openai" or "aoai".334 335    Returns:336        list: A list of configs for openai api calls.337    """338    return config_list_from_models(339        key_file_path,340        openai_api_key_file,341        aoai_api_key_file,342        aoai_api_base_file,343        exclude,344        model_list=["gpt-4", "gpt-3.5-turbo"],345    )346 347 348def filter_config(config_list, filter_dict):349    """350    This function filters `config_list` by checking each configuration dictionary against the351    criteria specified in `filter_dict`. A configuration dictionary is retained if for every352    key in `filter_dict`, see example below.353 354    Args:355        config_list (list of dict): A list of configuration dictionaries to be filtered.356        filter_dict (dict): A dictionary representing the filter criteria, where each key is a357                            field name to check within the configuration dictionaries, and the358                            corresponding value is a list of acceptable values for that field.359 360    Returns:361        list of dict: A list of configuration dictionaries that meet all the criteria specified362                      in `filter_dict`.363 364    Example:365    ```366        # Example configuration list with various models and API types367        configs = [368            {'model': 'gpt-3.5-turbo'},369            {'model': 'gpt-4'},370            {'model': 'gpt-3.5-turbo', 'api_type': 'azure'},371        ]372 373        # Define filter criteria to select configurations for the 'gpt-3.5-turbo' model374        # that are also using the 'azure' API type375        filter_criteria = {376            'model': ['gpt-3.5-turbo'],  # Only accept configurations for 'gpt-3.5-turbo'377            'api_type': ['azure']       # Only accept configurations for 'azure' API type378        }379 380        # Apply the filter to the configuration list381        filtered_configs = filter_config(configs, filter_criteria)382 383        # The resulting `filtered_configs` will be:384        # [{'model': 'gpt-3.5-turbo', 'api_type': 'azure', ...}]385    ```386 387    Note:388        - If `filter_dict` is empty or None, no filtering is applied and `config_list` is returned as is.389        - If a configuration dictionary in `config_list` does not contain a key specified in `filter_dict`,390          it is considered a non-match and is excluded from the result.391        - If the list of acceptable values for a key in `filter_dict` includes None, then configuration392          dictionaries that do not have that key will also be considered a match.393    """394    if filter_dict:395        config_list = [396            config for config in config_list if all(config.get(key) in value for key, value in filter_dict.items())397        ]398    return config_list399 400 401def config_list_from_json(402    env_or_file: str,403    file_location: Optional[str] = "",404    filter_dict: Optional[Dict[str, Union[List[Union[str, None]], Set[Union[str, None]]]]] = None,405) -> List[Dict]:406    """407    Retrieves a list of API configurations from a JSON stored in an environment variable or a file.408 409    This function attempts to parse JSON data from the given `env_or_file` parameter. If `env_or_file` is an410    environment variable containing JSON data, it will be used directly. Otherwise, it is assumed to be a filename,411    and the function will attempt to read the file from the specified `file_location`.412 413    The `filter_dict` parameter allows for filtering the configurations based on specified criteria. Each key in the414    `filter_dict` corresponds to a field in the configuration dictionaries, and the associated value is a list or set415    of acceptable values for that field. If a field is missing in a configuration and `None` is included in the list416    of acceptable values for that field, the configuration will still be considered a match.417 418    Args:419        env_or_file (str): The name of the environment variable, the filename, or the environment variable of the filename420            that containing the JSON data.421        file_location (str, optional): The directory path where the file is located, if `env_or_file` is a filename.422        filter_dict (dict, optional): A dictionary specifying the filtering criteria for the configurations, with423            keys representing field names and values being lists or sets of acceptable values for those fields.424 425    Example:426    ```427    # Suppose we have an environment variable 'CONFIG_JSON' with the following content:428    # '[{"model": "gpt-3.5-turbo", "api_type": "azure"}, {"model": "gpt-4"}]'429 430    # We can retrieve a filtered list of configurations like this:431    filter_criteria = {"model": ["gpt-3.5-turbo"]}432    configs = config_list_from_json('CONFIG_JSON', filter_dict=filter_criteria)433    # The 'configs' variable will now contain only the configurations that match the filter criteria.434    ```435 436    Returns:437        List[Dict]: A list of configuration dictionaries that match the filtering criteria specified in `filter_dict`.438 439    Raises:440        FileNotFoundError: if env_or_file is neither found as an environment variable nor a file441    """442    env_str = os.environ.get(env_or_file)443 444    if env_str:445        # The environment variable exists. We should use information from it.446        if os.path.exists(env_str):447            # It is a file location, and we need to load the json from the file.448            with open(env_str, "r") as file:449                json_str = file.read()450        else:451            # Else, it should be a JSON string by itself.452            json_str = env_str453        config_list = json.loads(json_str)454    else:455        # The environment variable does not exist.456        # So, `env_or_file` is a filename. We should use the file location.457        config_list_path = os.path.join(file_location, env_or_file)458        with open(config_list_path) as json_file:459            config_list = json.load(json_file)460    return filter_config(config_list, filter_dict)461 462 463def get_config(464    api_key: str, base_url: Optional[str] = None, api_type: Optional[str] = None, api_version: Optional[str] = None465) -> Dict:466    """467    Constructs a configuration dictionary for a single model with the provided API configurations.468 469    Example:470    ```471    config = get_config(472        api_key="sk-abcdef1234567890",473        base_url="https://api.openai.com",474        api_version="v1"475    )476    # The 'config' variable will now contain:477    # {478    #     "api_key": "sk-abcdef1234567890",479    #     "base_url": "https://api.openai.com",480    #     "api_version": "v1"481    # }482    ```483 484    Args:485        api_key (str): The API key for authenticating API requests.486        base_url (Optional[str]): The base URL of the API. If not provided, defaults to None.487        api_type (Optional[str]): The type of API. If not provided, defaults to None.488        api_version (Optional[str]): The version of the API. If not provided, defaults to None.489 490    Returns:491        Dict: A dictionary containing the provided API configurations.492    """493    config = {"api_key": api_key}494    if base_url:495        config["base_url"] = base_url496    if api_type:497        config["api_type"] = api_type498    if api_version:499        config["api_version"] = api_version500    return config501 502 503def config_list_from_dotenv(504    dotenv_file_path: Optional[str] = None, model_api_key_map: Optional[dict] = None, filter_dict: Optional[dict] = None505) -> List[Dict[str, Union[str, Set[str]]]]:506    """507    Load API configurations from a specified .env file or environment variables and construct a list of configurations.508 509    This function will:510    - Load API keys from a provided .env file or from existing environment variables.511    - Create a configuration dictionary for each model using the API keys and additional configurations.512    - Filter and return the configurations based on provided filters.513 514    model_api_key_map will default to `{"gpt-4": "OPENAI_API_KEY", "gpt-3.5-turbo": "OPENAI_API_KEY"}` if none515 516    Args:517        dotenv_file_path (str, optional): The path to the .env file. Defaults to None.518        model_api_key_map (str/dict, optional): A dictionary mapping models to their API key configurations.519                                           If a string is provided as configuration, it is considered as an environment520                                           variable name storing the API key.521                                           If a dict is provided, it should contain at least 'api_key_env_var' key,522                                           and optionally other API configurations like 'base_url', 'api_type', and 'api_version'.523                                           Defaults to a basic map with 'gpt-4' and 'gpt-3.5-turbo' mapped to 'OPENAI_API_KEY'.524        filter_dict (dict, optional): A dictionary containing the models to be loaded.525                                      Containing a 'model' key mapped to a set of model names to be loaded.526                                      Defaults to None, which loads all found configurations.527 528    Returns:529        List[Dict[str, Union[str, Set[str]]]]: A list of configuration dictionaries for each model.530 531    Raises:532        FileNotFoundError: If the specified .env file does not exist.533        TypeError: If an unsupported type of configuration is provided in model_api_key_map.534    """535    if dotenv_file_path:536        dotenv_path = Path(dotenv_file_path)537        if dotenv_path.exists():538            load_dotenv(dotenv_path)539        else:540            logging.warning(f"The specified .env file {dotenv_path} does not exist.")541    else:542        dotenv_path = find_dotenv()543        if not dotenv_path:544            logging.warning("No .env file found. Loading configurations from environment variables.")545        load_dotenv(dotenv_path)546 547    # Ensure the model_api_key_map is not None to prevent TypeErrors during key assignment.548    model_api_key_map = model_api_key_map or {}549 550    # Ensure default models are always considered551    default_models = ["gpt-4", "gpt-3.5-turbo"]552 553    for model in default_models:554        # Only assign default API key if the model is not present in the map.555        # If model is present but set to invalid/empty, do not overwrite.556        if model not in model_api_key_map:557            model_api_key_map[model] = "OPENAI_API_KEY"558 559    env_var = []560    # Loop over the models and create configuration dictionaries561    for model, config in model_api_key_map.items():562        if isinstance(config, str):563            api_key_env_var = config564            config_dict = get_config(api_key=os.getenv(api_key_env_var))565        elif isinstance(config, dict):566            api_key = os.getenv(config.get("api_key_env_var", "OPENAI_API_KEY"))567            config_without_key_var = {k: v for k, v in config.items() if k != "api_key_env_var"}568            config_dict = get_config(api_key=api_key, **config_without_key_var)569        else:570            logging.warning(f"Unsupported type {type(config)} for model {model} configuration")571 572        if not config_dict["api_key"] or config_dict["api_key"].strip() == "":573            logging.warning(574                f"API key not found or empty for model {model}. Please ensure path to .env file is correct."575            )576            continue  # Skip this configuration and continue with the next577 578        # Add model to the configuration and append to the list579        config_dict["model"] = model580        env_var.append(config_dict)581 582    fd, temp_name = tempfile.mkstemp()583    try:584        with os.fdopen(fd, "w+") as temp:585            env_var_str = json.dumps(env_var)586            temp.write(env_var_str)587            temp.flush()588 589            # Assuming config_list_from_json is a valid function from your code590            config_list = config_list_from_json(env_or_file=temp_name, filter_dict=filter_dict)591    finally:592        # The file is deleted after using its name (to prevent windows build from breaking)593        os.remove(temp_name)594 595    if len(config_list) == 0:596        logging.error("No configurations loaded.")597        return []598 599    logging.info(f"Models available: {[config['model'] for config in config_list]}")600    return config_list601 602 603def retrieve_assistants_by_name(client: OpenAI, name: str) -> List[Assistant]:604    """605    Return the assistants with the given name from OAI assistant API606    """607    if ERROR:608        raise ERROR609    assistants = client.beta.assistants.list()610    candidate_assistants = []611    for assistant in assistants.data:612        if assistant.name == name:613            candidate_assistants.append(assistant)614    return candidate_assistants615