cd into the submodule directory, you can git pull and git push to that repository.
safety-tooling as a submodule here: https://github.com/safety-research/safety-examplesTo set up the development environment for this project, follow the steps below:
uv to manage the python environment. Install it with the following command:curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
git clone git@github.com:safety-research/safety-tooling.git
cd safety-tooling
uv venv --python=python3.11
source .venv/bin/activate
uv pip install -e .
uv pip install -r requirements_dev.txt
python -m ipykernel install --user --name=venv
If you don't expect to make any changes to the package (not recommended when actively doing research), you can install it directly from pip by running the following command. This is not recommended when actively doing research but a great option once you release your code.
pip install git+https://github.com/safety-research/safety-tooling.git@<branch-name>#egg=safetytooling
You should copy the .env.example file to .env at the root of the repository and fill in the API keys. All are optional but features that rely on them will not work if they are not set.
OPENAI_API_KEY=<your-key>
ANTHROPIC_API_KEY=<your-key>
HF_TOKEN=<your-key>
GOOGLE_API_KEY=<your-key>
GRAYSWAN_API_KEY=<your-key>
TOGETHER_API_KEY=<your-key>
DEEPSEEK_API_KEY=<your-key>
ELEVENLABS_API_KEY=<your-key>
You can add multiple OpenAI and Anthropic API keys by adding them to the .env file with different names. You can then pass the openai_tag and anthropic_tag to utils.setup_environment() to switch between them. Alternatively, pass openai_api_key and anthropic_api_key to the InferenceAPI object directly.
We lint our code with Ruff and format with black. This tool is automatically installed when you run set up the development environment.
If you use vscode, it's recommended to install the official Ruff extension.
In addition, there is a pre-commit hook that runs the linter and formatter before you commit.
To enable it, run make hooks but this is optional.
To use Redis for caching instead of writing everything to disk, install Redis, and make sure it's running by doing redis-cli ping.
export REDIS_CACHE=True # Enable Redis caching (defaults to False)
export REDIS_PASSWORD=<your-password> # Optional Redis password, in case the Redis instance on your machine is password protected
Default Redis configuration if not specified:
You can monitor what is being read from or written to Redis by running redis-cli and then MONITOR.
Run tests as a Python module (with 6 parallel workers, and verbose output) using:
python -m pytest -v -s -n 6
Certain tests are inherently slow, including all tests regarding the batch API. We disable them by default to avoid slowing down the CI pipeline. To run them, use:
SAFETYTOOLING_SLOW_TESTS=True python -m pytest -v -s -n 6
We only pin top-level dependencies only to make cross-platform development easier.
pyproject.toml. If it is only for development, add it to requirements_dev.txt.pyproject.toml.To check for outdated dependencies, run uv pip list --outdated.
Minimal example to run inference for gpt-4o-mini. See examples/inference_api/inference_api.ipynb to quickly run this example.
from safetytooling.apis import InferenceAPI
from safetytooling.data_models import ChatMessage, MessageRole, Prompt
from safetytooling.utils import utils
from pathlib import Path
utils.setup_environment()
API = InferenceAPI(cache_dir=Path(".cache"))
prompt = Prompt(messages=[ChatMessage(content="What is your name?", role=MessageRole.user)])
response = await API(
model_id="gpt-4o-mini",
prompt=prompt,
print_prompt_and_response=True,
)
The InferenceAPI class supports running new models when they come out without needing to update the codebase. However, you have to pass force_provider to the API object call. For example, if you want to run gpt-4-new-model, you can do:
response = await API(
model_id="gpt-4-new-model",
prompt=prompt,
force_provider="openai"
)
Note: setup_environment() will automatically load the API keys and set the environment variables. You can set custom API keys by setting the environment variables instead of calling setup_environment(). If you have multiple API keys for OpenAI and Anthropic, you can pass openai_tag and anthropic_tag to setup_environment() to choose those to be exported.
utils.setup_environment(openai_tag="OPENAI_API_KEY_CUSTOM", anthropic_tag="ANTHROPIC_API_KEY_CUSTOM")
See examples/anthropic_batch_api/run_anthropic_batch.py for an example of how to use the Anthropic Batch API and how to set up command line input arguments using simple_parsing and ExperimentConfigBase (a useful base class we created for this project).
If you want to use a different provider that uses an OpenAI compatible api, you can just override the base_url when creating an InferenceAPI and then doing force_provider="openai" when calling it. E.g.
API = InferenceAPI(cache_dir=Path(".cache"), openai_base_url="https://openrouter.ai/api/v1", openai_api_key=openrouter_api_key)
response = await API(
model_id="deepseek/deepseek-v3-base:free",
prompt=base_prompt,
max_tokens=100,
print_prompt_and_response=True,
temperature=0,
force_provider="openai",
)
We make this easy to run a server locally and hook into the InferenceAPI. Here is a snippet and it is also in the examples/inference_api/vllm_api.ipynb notebook.
from safetytooling.apis import InferenceAPI
from safetytooling.data_models import ChatMessage, MessageRole, Prompt
from safetytooling.utils import utils
from safetytooling.utils.vllm_utils import deploy_model_vllm_locally_auto
utils.setup_environment()
server = await deploy_model_vllm_locally_auto("meta-llama/Llama-3.1-8B-Instruct", max_model_len=1024, max_num_seqs=32)
API = InferenceAPI(vllm_base_url=f"{server.base_url}/v1/chat/completions", vllm_num_threads=32, use_vllm_if_model_not_found=True)
prompt = Prompt(messages=[ChatMessage(content="What is your name?", role=MessageRole.user)])
response = await API(
model_id=server.model_name,
prompt=prompt,
print_prompt_and_response=True,
)
To launch a finetuning job, run the following command:
python -m safetytooling.apis.finetuning.openai.run --model 'gpt-3.5-turbo-1106' --train_file <path-to-train-file> --n_epochs 1
This should automatically create a new job on the OpenAI API, and also sync that run to wandb. You will have to keep the program running until the OpenAI job is complete.
You can include the --dry_run flag if you just want to validate the train/val files and estimate the training cost without actually launching a job.
To get OpenAI usage stats, run:
python -m safetytooling.apis.inference.usage.usage_openai
You can pass a list of models to get usage stats for specific models. For example:
python -m safetytooling.apis.inference.usage.usage_openai --models 'model-id1' 'model-id2' --openai_tags 'OPENAI_API_KEY1' 'OPENAI_API_KEY2'
And for Anthropic, to fine out the numbder of threads being used run:
python -m safetytooling.apis.inference.usage.usage_anthropic
safetytooling/apis/inference/api.py$exp_dir/cache. This means you can kill your run anytime and restart it without worrying about wasting API calls.REDIS_CACHE=true in your environment. Configure Redis connection with:
REDIS_PASSWORD: Optional Redis password for authenticationREDIS_CACHE environment variable.NO_CACHE=True as an environment variable to disable all caching. This is equivalent to setting use_cache=False when initialising an InferenceAPI or BatchInferenceAPI object..txt files are can be output in $exp_dir/prompt_history and timestamped for easy reference (off by default). You can also pass print_prompt_and_response to the api object to print coloured messages to the terminal.openai_num_threads, anthropic_num_threads). Furthermore, the fraction of the OpenAI rate limit can be specified (e.g. only using 50% rate limit by setting openai_fraction_rate_limit=0.5.is_valid function and will retry until a valid one is generated (e.g. ensuring json output).max_tokens=None for OpenAI models.safetytooling/data_models/messages.pyPrompt class to see how the messages are transformed into different formats. This is important if you ever need to add new providers or modalities.safetytooling/apis/finetuning/run.pysafetytooling/apis/tts/elevenlabs.pysafetytooling/apis/inference/usagesafetytooling/utils/experiment_utils.pycfg.api.examples repo in next section.setup_environment() in safetytooling/uils/utils.py loads these in so they are accessible by the code (and also automates exporting environment variables)plotting_utils.py)prompt_utils.py)image_utils.py and audio_utils.py)human_labeling_utils.py)If you use this repo in your work, please cite it as follows:
@misc{safety_tooling_2025,
author = {John Hughes and safety-research},
title = {safety-research/safety-tooling: v1.0.0},
year = {2025},
publisher = {Zenodo},
version = {v1.0.0},
doi = {10.5281/zenodo.15363603},
url = {https://doi.org/10.5281/zenodo.15363603}
}
Python
99.0%
cd into the submodule directory, you can git pull and git push to that repository.
safety-tooling as a submodule here: https://github.com/safety-research/safety-examplesTo set up the development environment for this project, follow the steps below:
uv to manage the python environment. Install it with the following command:curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
git clone git@github.com:safety-research/safety-tooling.git
cd safety-tooling
uv venv --python=python3.11
source .venv/bin/activate
uv pip install -e .
uv pip install -r requirements_dev.txt
python -m ipykernel install --user --name=venv
If you don't expect to make any changes to the package (not recommended when actively doing research), you can install it directly from pip by running the following command. This is not recommended when actively doing research but a great option once you release your code.
pip install git+https://github.com/safety-research/safety-tooling.git@<branch-name>#egg=safetytooling
You should copy the .env.example file to .env at the root of the repository and fill in the API keys. All are optional but features that rely on them will not work if they are not set.
OPENAI_API_KEY=<your-key>
ANTHROPIC_API_KEY=<your-key>
HF_TOKEN=<your-key>
GOOGLE_API_KEY=<your-key>
GRAYSWAN_API_KEY=<your-key>
TOGETHER_API_KEY=<your-key>
DEEPSEEK_API_KEY=<your-key>
ELEVENLABS_API_KEY=<your-key>
You can add multiple OpenAI and Anthropic API keys by adding them to the .env file with different names. You can then pass the openai_tag and anthropic_tag to utils.setup_environment() to switch between them. Alternatively, pass openai_api_key and anthropic_api_key to the InferenceAPI object directly.
We lint our code with Ruff and format with black. This tool is automatically installed when you run set up the development environment.
If you use vscode, it's recommended to install the official Ruff extension.
In addition, there is a pre-commit hook that runs the linter and formatter before you commit.
To enable it, run make hooks but this is optional.
To use Redis for caching instead of writing everything to disk, install Redis, and make sure it's running by doing redis-cli ping.
export REDIS_CACHE=True # Enable Redis caching (defaults to False)
export REDIS_PASSWORD=<your-password> # Optional Redis password, in case the Redis instance on your machine is password protected
Default Redis configuration if not specified:
You can monitor what is being read from or written to Redis by running redis-cli and then MONITOR.
Run tests as a Python module (with 6 parallel workers, and verbose output) using:
python -m pytest -v -s -n 6
Certain tests are inherently slow, including all tests regarding the batch API. We disable them by default to avoid slowing down the CI pipeline. To run them, use:
SAFETYTOOLING_SLOW_TESTS=True python -m pytest -v -s -n 6
We only pin top-level dependencies only to make cross-platform development easier.
pyproject.toml. If it is only for development, add it to requirements_dev.txt.pyproject.toml.To check for outdated dependencies, run uv pip list --outdated.
Minimal example to run inference for gpt-4o-mini. See examples/inference_api/inference_api.ipynb to quickly run this example.
from safetytooling.apis import InferenceAPI
from safetytooling.data_models import ChatMessage, MessageRole, Prompt
from safetytooling.utils import utils
from pathlib import Path
utils.setup_environment()
API = InferenceAPI(cache_dir=Path(".cache"))
prompt = Prompt(messages=[ChatMessage(content="What is your name?", role=MessageRole.user)])
response = await API(
model_id="gpt-4o-mini",
prompt=prompt,
print_prompt_and_response=True,
)
The InferenceAPI class supports running new models when they come out without needing to update the codebase. However, you have to pass force_provider to the API object call. For example, if you want to run gpt-4-new-model, you can do:
response = await API(
model_id="gpt-4-new-model",
prompt=prompt,
force_provider="openai"
)
Note: setup_environment() will automatically load the API keys and set the environment variables. You can set custom API keys by setting the environment variables instead of calling setup_environment(). If you have multiple API keys for OpenAI and Anthropic, you can pass openai_tag and anthropic_tag to setup_environment() to choose those to be exported.
utils.setup_environment(openai_tag="OPENAI_API_KEY_CUSTOM", anthropic_tag="ANTHROPIC_API_KEY_CUSTOM")
See examples/anthropic_batch_api/run_anthropic_batch.py for an example of how to use the Anthropic Batch API and how to set up command line input arguments using simple_parsing and ExperimentConfigBase (a useful base class we created for this project).
If you want to use a different provider that uses an OpenAI compatible api, you can just override the base_url when creating an InferenceAPI and then doing force_provider="openai" when calling it. E.g.
API = InferenceAPI(cache_dir=Path(".cache"), openai_base_url="https://openrouter.ai/api/v1", openai_api_key=openrouter_api_key)
response = await API(
model_id="deepseek/deepseek-v3-base:free",
prompt=base_prompt,
max_tokens=100,
print_prompt_and_response=True,
temperature=0,
force_provider="openai",
)
We make this easy to run a server locally and hook into the InferenceAPI. Here is a snippet and it is also in the examples/inference_api/vllm_api.ipynb notebook.
from safetytooling.apis import InferenceAPI
from safetytooling.data_models import ChatMessage, MessageRole, Prompt
from safetytooling.utils import utils
from safetytooling.utils.vllm_utils import deploy_model_vllm_locally_auto
utils.setup_environment()
server = await deploy_model_vllm_locally_auto("meta-llama/Llama-3.1-8B-Instruct", max_model_len=1024, max_num_seqs=32)
API = InferenceAPI(vllm_base_url=f"{server.base_url}/v1/chat/completions", vllm_num_threads=32, use_vllm_if_model_not_found=True)
prompt = Prompt(messages=[ChatMessage(content="What is your name?", role=MessageRole.user)])
response = await API(
model_id=server.model_name,
prompt=prompt,
print_prompt_and_response=True,
)
To launch a finetuning job, run the following command:
python -m safetytooling.apis.finetuning.openai.run --model 'gpt-3.5-turbo-1106' --train_file <path-to-train-file> --n_epochs 1
This should automatically create a new job on the OpenAI API, and also sync that run to wandb. You will have to keep the program running until the OpenAI job is complete.
You can include the --dry_run flag if you just want to validate the train/val files and estimate the training cost without actually launching a job.
To get OpenAI usage stats, run:
python -m safetytooling.apis.inference.usage.usage_openai
You can pass a list of models to get usage stats for specific models. For example:
python -m safetytooling.apis.inference.usage.usage_openai --models 'model-id1' 'model-id2' --openai_tags 'OPENAI_API_KEY1' 'OPENAI_API_KEY2'
And for Anthropic, to fine out the numbder of threads being used run:
python -m safetytooling.apis.inference.usage.usage_anthropic
safetytooling/apis/inference/api.py$exp_dir/cache. This means you can kill your run anytime and restart it without worrying about wasting API calls.REDIS_CACHE=true in your environment. Configure Redis connection with:
REDIS_PASSWORD: Optional Redis password for authenticationREDIS_CACHE environment variable.NO_CACHE=True as an environment variable to disable all caching. This is equivalent to setting use_cache=False when initialising an InferenceAPI or BatchInferenceAPI object..txt files are can be output in $exp_dir/prompt_history and timestamped for easy reference (off by default). You can also pass print_prompt_and_response to the api object to print coloured messages to the terminal.openai_num_threads, anthropic_num_threads). Furthermore, the fraction of the OpenAI rate limit can be specified (e.g. only using 50% rate limit by setting openai_fraction_rate_limit=0.5.is_valid function and will retry until a valid one is generated (e.g. ensuring json output).max_tokens=None for OpenAI models.safetytooling/data_models/messages.pyPrompt class to see how the messages are transformed into different formats. This is important if you ever need to add new providers or modalities.safetytooling/apis/finetuning/run.pysafetytooling/apis/tts/elevenlabs.pysafetytooling/apis/inference/usagesafetytooling/utils/experiment_utils.pycfg.api.examples repo in next section.setup_environment() in safetytooling/uils/utils.py loads these in so they are accessible by the code (and also automates exporting environment variables)plotting_utils.py)prompt_utils.py)image_utils.py and audio_utils.py)human_labeling_utils.py)If you use this repo in your work, please cite it as follows:
@misc{safety_tooling_2025,
author = {John Hughes and safety-research},
title = {safety-research/safety-tooling: v1.0.0},
year = {2025},
publisher = {Zenodo},
version = {v1.0.0},
doi = {10.5281/zenodo.15363603},
url = {https://doi.org/10.5281/zenodo.15363603}
}
Python
99.0%