Skip to content

Commit

Permalink
openai_utils.py - functionality for instantiating config_list with a …
Browse files Browse the repository at this point in the history
….env file (microsoft#68)

* FORMATTING

* UPDATE - OAI __init__.py

* ruff

* ADD - notebook covering oai API configuration options and their different purposes

* ADD openai util updates so that the function just assumes the same environment variable name for all models, also added functionality for adding API configurations like api_base etc.

* ADD - updates to config_list_from_dotenv and tests for openai_util testing, update example notebook

* UPDATE - added working config_list_from_dotenv() with passing tests, and updated notebook

* UPDATE - code and tests to potentially get around the window build permission error, used different method of producing temporary files

---------

Co-authored-by: Ward <[email protected]>
  • Loading branch information
AaronWard and Ward authored Oct 5, 2023
1 parent dc70b80 commit 5c2a268
Show file tree
Hide file tree
Showing 5 changed files with 798 additions and 3 deletions.
2 changes: 2 additions & 0 deletions autogen/oai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
config_list_openai_aoai,
config_list_from_models,
config_list_from_json,
config_list_from_dotenv,
)

__all__ = [
Expand All @@ -15,4 +16,5 @@
"config_list_openai_aoai",
"config_list_from_models",
"config_list_from_json",
"config_list_from_dotenv",
]
140 changes: 140 additions & 0 deletions autogen/oai/openai_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import os
import json
import tempfile
from pathlib import Path
from typing import List, Optional, Dict, Set, Union
import logging
from dotenv import find_dotenv, load_dotenv


NON_CACHE_KEY = ["api_key", "api_base", "api_type", "api_version"]

Expand Down Expand Up @@ -239,3 +243,139 @@ def config_list_from_json(
except FileNotFoundError:
return []
return filter_config(config_list, filter_dict)


def get_config(
api_key: str, api_base: Optional[str] = None, api_type: Optional[str] = None, api_version: Optional[str] = None
) -> Dict:
"""
Construct a configuration dictionary with the provided API configurations.
Appending the additional configurations to the config only if they're set
example:
>> model_api_key_map={
"gpt-4": "OPENAI_API_KEY",
"gpt-3.5-turbo": {
"api_key_env_var": "ANOTHER_API_KEY",
"api_type": "aoai",
"api_version": "v2",
"api_base": "https://api.someotherapi.com"
}
}
Args:
api_key (str): The API key used for authenticating API requests.
api_base (str, optional): The base URL of the API. Defaults to None.
api_type (str, optional): The type or kind of API. Defaults to None.
api_version (str, optional): The API version. Defaults to None.
Returns:
Dict: A dictionary containing the API configurations.
"""
config = {"api_key": api_key}
if api_base:
config["api_base"] = api_base
if api_type:
config["api_type"] = api_type
if api_version:
config["api_version"] = api_version
return config


def config_list_from_dotenv(
dotenv_file_path: Optional[str] = None, model_api_key_map: Optional[dict] = None, filter_dict: Optional[dict] = None
) -> List[Dict[str, Union[str, Set[str]]]]:
"""
Load API configurations from a specified .env file or environment variables and construct a list of configurations.
This function will:
- Load API keys from a provided .env file or from existing environment variables.
- Create a configuration dictionary for each model using the API keys and additional configurations.
- Filter and return the configurations based on provided filters.
model_api_key_map will default to `{"gpt-4": "OPENAI_API_KEY", "gpt-3.5-turbo": "OPENAI_API_KEY"}` if none
Args:
dotenv_file_path (str, optional): The path to the .env file. Defaults to None.
model_api_key_map (str/dict, optional): A dictionary mapping models to their API key configurations.
If a string is provided as configuration, it is considered as an environment
variable name storing the API key.
If a dict is provided, it should contain at least 'api_key_env_var' key,
and optionally other API configurations like 'api_base', 'api_type', and 'api_version'.
Defaults to a basic map with 'gpt-4' and 'gpt-3.5-turbo' mapped to 'OPENAI_API_KEY'.
filter_dict (dict, optional): A dictionary containing the models to be loaded.
Containing a 'model' key mapped to a set of model names to be loaded.
Defaults to None, which loads all found configurations.
Returns:
List[Dict[str, Union[str, Set[str]]]]: A list of configuration dictionaries for each model.
Raises:
FileNotFoundError: If the specified .env file does not exist.
TypeError: If an unsupported type of configuration is provided in model_api_key_map.
"""
if dotenv_file_path:
dotenv_path = Path(dotenv_file_path)
if dotenv_path.exists():
load_dotenv(dotenv_path)
else:
logging.warning(f"The specified .env file {dotenv_path} does not exist.")
else:
dotenv_path = find_dotenv()
if not dotenv_path:
logging.warning("No .env file found. Loading configurations from environment variables.")
load_dotenv(dotenv_path)

# Ensure the model_api_key_map is not None to prevent TypeErrors during key assignment.
model_api_key_map = model_api_key_map or {}

# Ensure default models are always considered
default_models = ["gpt-4", "gpt-3.5-turbo"]

for model in default_models:
# Only assign default API key if the model is not present in the map.
# If model is present but set to invalid/empty, do not overwrite.
if model not in model_api_key_map:
model_api_key_map[model] = "OPENAI_API_KEY"

env_var = []
# Loop over the models and create configuration dictionaries
for model, config in model_api_key_map.items():
if isinstance(config, str):
api_key_env_var = config
config_dict = get_config(api_key=os.getenv(api_key_env_var))
elif isinstance(config, dict):
api_key = os.getenv(config.get("api_key_env_var", "OPENAI_API_KEY"))
config_without_key_var = {k: v for k, v in config.items() if k != "api_key_env_var"}
config_dict = get_config(api_key=api_key, **config_without_key_var)
else:
logging.warning(f"Unsupported type {type(config)} for model {model} configuration")

if not config_dict["api_key"] or config_dict["api_key"].strip() == "":
logging.warning(
f"API key not found or empty for model {model}. Please ensure path to .env file is correct."
)
continue # Skip this configuration and continue with the next

# Add model to the configuration and append to the list
config_dict["model"] = model
env_var.append(config_dict)

fd, temp_name = tempfile.mkstemp()
try:
with os.fdopen(fd, "w+") as temp:
env_var_str = json.dumps(env_var)
temp.write(env_var_str)
temp.flush()

# Assuming config_list_from_json is a valid function from your code
config_list = config_list_from_json(env_or_file=temp_name, filter_dict=filter_dict)
finally:
# The file is deleted after using its name (to prevent windows build from breaking)
os.remove(temp_name)

if len(config_list) == 0:
logging.error("No configurations loaded.")
return []

logging.info(f"Models available: {[config['model'] for config in config_list]}")
return config_list
Loading

0 comments on commit 5c2a268

Please sign in to comment.