Skip to content

Commit

Permalink
ADD - updates to config_list_from_dotenv and tests for openai_util te…
Browse files Browse the repository at this point in the history
…sting, update example notebook
  • Loading branch information
Ward authored and Ward committed Oct 4, 2023
1 parent d529672 commit b1f9f01
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 43 deletions.
54 changes: 48 additions & 6 deletions autogen/oai/openai_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,29 @@ def config_list_from_json(
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
Expand All @@ -262,14 +285,33 @@ 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]]]]:
"""
[Rest of the docstring is omitted for brevity]
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.
model_api_key_map (dict, optional): A dictionary mapping models to their configuration.
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.
[Rest of the docstring is omitted for brevity]
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)
Expand Down Expand Up @@ -297,14 +339,14 @@ def config_list_from_dotenv(
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"))
# Exclude 'api_key_env_var' from config before passing it to get_config()
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:
raise TypeError(f"Unsupported type {type(config)} for model {model} configuration")

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

config_dict["model"] = model
env_var.append(config_dict)
Expand Down
27 changes: 21 additions & 6 deletions notebook/oai_openai_utils.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -312,12 +312,20 @@
" \"gpt-4\": \"OPENAI_API_KEY\",\n",
" \"gpt-3.5-turbo\": \"OPENAI_API_KEY\",\n",
" }\n",
"```\n",
"\n",
"Here is an example `.env` file:\n",
"\n",
"```bash\n",
"OPENAI_API_KEY=sk-*********************\n",
"HUGGING_FACE_API_KEY=**************************\n",
"ANOTHER_API_KEY=1234567890234567890\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 1,
"metadata": {},
"outputs": [
{
Expand All @@ -326,7 +334,7 @@
"[{'api_key': 'sk-*********************', 'model': 'gpt-4'}]"
]
},
"execution_count": 3,
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
Expand All @@ -352,7 +360,7 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 2,
"metadata": {},
"outputs": [
{
Expand All @@ -362,7 +370,7 @@
" {'api_key': '**************************', 'model': 'vicuna'}]"
]
},
"execution_count": 1,
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
Expand Down Expand Up @@ -395,7 +403,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 3,
"metadata": {},
"outputs": [
{
Expand All @@ -409,7 +417,7 @@
" 'model': 'gpt-3.5-turbo'}]"
]
},
"execution_count": 5,
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
Expand All @@ -436,6 +444,13 @@
"\n",
"config_list"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
Expand Down
106 changes: 75 additions & 31 deletions test/oai/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,75 +1,119 @@
import os
import sys
import json
import os
import pytest
import logging
import tempfile
from unittest import mock
from test_completion import KEY_LOC, OAI_CONFIG_LIST

sys.path.append("../../autogen")
import autogen # noqa: E402

# Example environment variables
ENV_VARS = {
"OPENAI_API_KEY": "sk-********************",
"HUGGING_FACE_API_KEY": "**************************",
"ANOTHER_API_KEY": "1234567890234567890",
}

# Example model to API key mappings
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",
},
}

# Example filter dictionary
FILTER_DICT = {
"model": {
"gpt-4",
"gpt-3.5-turbo",
}
}


@pytest.fixture
def mock_os_environ():
with mock.patch.dict(os.environ, ENV_VARS):
yield


def test_config_list_from_json():
# Test the functionality for loading configurations from JSON file
# and ensuring that the loaded configurations are as expected.
config_list = autogen.config_list_gpt4_gpt35(key_file_path=KEY_LOC)
json_file = os.path.join(KEY_LOC, "config_list_test.json")

with open(json_file, "w") as f:
json.dump(config_list, f, indent=4)

config_list_1 = autogen.config_list_from_json(json_file)
assert config_list == config_list_1

os.environ["config_list_test"] = json.dumps(config_list)
config_list_2 = autogen.config_list_from_json("config_list_test")
assert config_list == config_list_2

config_list_3 = autogen.config_list_from_json(
OAI_CONFIG_LIST, file_location=KEY_LOC, filter_dict={"model": ["gpt4", "gpt-4-32k"]}
)
assert all(config.get("model") in ["gpt4", "gpt-4-32k"] for config in config_list_3)

del os.environ["config_list_test"]
os.remove(json_file)


def test_config_list_openai_aoai():
# Testing the functionality for loading configurations for different API types
# and ensuring the API types in the loaded configurations are as expected.
config_list = autogen.config_list_openai_aoai(key_file_path=KEY_LOC)
assert all(config.get("api_type") in [None, "open_ai", "azure"] for config in config_list)


@pytest.fixture
def dotenv_file():
def test_config_list_from_dotenv(mock_os_environ, caplog):
# Test with valid .env file
with tempfile.NamedTemporaryFile(mode="w+", delete=True) as temp:
temp.write("OPENAI_API_KEY=SomeAPIKey")
temp.write("\n".join([f"{k}={v}" for k, v in ENV_VARS.items()]))
temp.flush()
yield temp.name

config_list = autogen.config_list_from_dotenv(
dotenv_file_path=temp.name, model_api_key_map=MODEL_API_KEY_MAP, filter_dict=FILTER_DICT
)

def test_config_list_from_dotenv(dotenv_file):
api_key_env_var = "OPENAI_API_KEY"
# Test valid case
config_list = autogen.config_list_from_dotenv(dotenv_file_path=dotenv_file)
assert config_list, "Configuration list is empty in valid case"
assert all(config["api_key"] == "SomeAPIKey" for config in config_list), "API Key mismatch in valid case"
# Ensure configurations are loaded and API keys match expected values
assert config_list, "Config list is empty"
for config in config_list:
api_key_info = MODEL_API_KEY_MAP[config["model"]]
api_key_var_name = api_key_info if isinstance(api_key_info, str) else api_key_info["api_key_env_var"]
assert config["api_key"] == ENV_VARS[api_key_var_name], "API Key mismatch in valid case"

# Test invalid path case
with pytest.raises(FileNotFoundError, match="The specified .env file invalid_path does not exist."):
autogen.config_list_from_dotenv(dotenv_file_path="invalid_path")
# Test with missing dotenv file
with pytest.raises(FileNotFoundError, match=r"The specified \.env file .* does not exist\."):
autogen.config_list_from_dotenv(dotenv_file_path="non_existent_path")

# Test no API key case
with tempfile.NamedTemporaryFile(mode="w+", delete=True) as temp:
temp.write("DIFFERENT_API_KEY=SomeAPIKey")
temp.flush()
# Test with invalid API key
ENV_VARS["ANOTHER_API_KEY"] = "" # Removing ANOTHER_API_KEY value

# Remove the OPENAI_API_KEY from environment variables if it exists
original_api_key = os.environ.pop(api_key_env_var, None)
with caplog.at_level(logging.WARNING):
result = autogen.config_list_from_dotenv(model_api_key_map=MODEL_API_KEY_MAP)
assert "No .env file found. Loading configurations from environment variables." in caplog.text
# The function does not return an empty list if at least one configuration is loaded successfully
assert result != [], "Config list is empty"

try:
# Explicitly check for ValueError due to missing API key
with pytest.raises(
ValueError, match=f"{api_key_env_var} not found or empty. Please ensure path to .env file is correct."
):
autogen.config_list_from_dotenv(dotenv_file_path=temp.name)
finally:
# Restore the original OPENAI_API_KEY in environment variables after the test
if original_api_key is not None:
os.environ["OPENAI_API_KEY"] = original_api_key
# Test with no configurations loaded
invalid_model_api_key_map = {
"gpt-4": "INVALID_API_KEY", # Simulate an environment var name that doesn't exist
}
with caplog.at_level(logging.ERROR):
config_list = autogen.config_list_from_dotenv(model_api_key_map=invalid_model_api_key_map)
assert "No configurations loaded." in caplog.text
assert not config_list


if __name__ == "__main__":
test_config_list_from_json()
pytest.main()

0 comments on commit b1f9f01

Please sign in to comment.