Skip to content

feat: Allow aliases to include default prompt options #1189

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ See also [the llm tag](https://simonwillison.net/tags/llm/) on my blog.
* [Model aliases](https://llm.datasette.io/en/stable/aliases.html)
* [Listing aliases](https://llm.datasette.io/en/stable/aliases.html#listing-aliases)
* [Adding a new alias](https://llm.datasette.io/en/stable/aliases.html#adding-a-new-alias)
* [Alias with prompt options](https://llm.datasette.io/en/stable/aliases.html#aliases-with-options)
* [Removing an alias](https://llm.datasette.io/en/stable/aliases.html#removing-an-alias)
* [Viewing the aliases file](https://llm.datasette.io/en/stable/aliases.html#viewing-the-aliases-file)
* [Embeddings](https://llm.datasette.io/en/stable/embeddings/index.html)
Expand Down
40 changes: 40 additions & 0 deletions docs/aliases.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,46 @@ The `llm aliases remove <alias>` command will remove the specified alias:
llm aliases remove mini
```

## Aliases with options

You can save default prompt options with an alias. For example, to create an alias `4o-creative` that always uses `gpt-4o` with a high temperature, you can do this:

```bash
llm aliases set 4o-creative gpt-4o -o temperature 1.5
```

Then you can use this alias like so:

```bash
llm -m 4o-creative 'Write a creative story about a robot'
```

The model will be called with the `temperature` option set to `1.5`.

If you specify options both in the alias and on the command line, the command-line options will take precedence.

For example:

```bash
llm -m 4o-creative -o temperature 1.0 'Write a creative story about a robot'
```

This will override the alias temperature setting.

### OpenRouter plugin example

You can also create aliases for plugin models. Here is an example with openrouter provider configurations:

```bash
llm aliases set maverick-lowlatency openrouter/meta-llama/llama-4-maverick -o provider '{
"order": [
"Baseten",
"Parasail"
],
"allow_fallbacks": false
}'
```

## Viewing the aliases file

Aliases are stored in an `aliases.json` file in the LLM configuration directory.
Expand Down
73 changes: 71 additions & 2 deletions llm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,12 @@ def get_models_with_aliases() -> List["ModelWithAliases"]:
extra_model_aliases: Dict[str, list] = {}
if aliases_path.exists():
configured_aliases = json.loads(aliases_path.read_text())
for alias, model_id in configured_aliases.items():
for alias, model_id_or_config in configured_aliases.items():
# Handle both old format (string) and new format (dict with model and options)
if isinstance(model_id_or_config, dict) and "model" in model_id_or_config:
model_id = model_id_or_config["model"]
else:
model_id = model_id_or_config
extra_model_aliases.setdefault(model_id, []).append(alias)

def register(model, async_model=None, aliases=None):
Expand Down Expand Up @@ -222,7 +227,12 @@ def get_embedding_models_with_aliases() -> List["EmbeddingModelWithAliases"]:
extra_model_aliases: Dict[str, list] = {}
if aliases_path.exists():
configured_aliases = json.loads(aliases_path.read_text())
for alias, model_id in configured_aliases.items():
for alias, model_id_or_config in configured_aliases.items():
# Handle both old format (string) and new format (dict with model and options)
if isinstance(model_id_or_config, dict) and "model" in model_id_or_config:
model_id = model_id_or_config["model"]
else:
model_id = model_id_or_config
extra_model_aliases.setdefault(model_id, []).append(alias)

def register(model, aliases=None):
Expand Down Expand Up @@ -430,6 +440,65 @@ def set_alias(alias, model_id_or_alias):
path.write_text(json.dumps(current, indent=4) + "\n")


def set_alias_with_options(alias, model_id_or_alias, options):
"""
Set an alias to point to the specified model with default options.
"""
path = user_dir() / "aliases.json"
path.parent.mkdir(parents=True, exist_ok=True)
if not path.exists():
path.write_text("{}\n")
try:
current = json.loads(path.read_text())
except json.decoder.JSONDecodeError:
# We're going to write a valid JSON file in a moment:
current = {}
# Resolve model_id_or_alias to a model_id
try:
model = get_model(model_id_or_alias)
model_id = model.model_id
except UnknownModelError:
# Try to resolve it to an embedding model
try:
model = get_embedding_model(model_id_or_alias)
model_id = model.model_id
except UnknownModelError:
# Set the alias to the exact string they provided instead
model_id = model_id_or_alias
# Store as a dictionary with model and options
current[alias] = {
"model": model_id,
"options": options
}
path.write_text(json.dumps(current, indent=4) + "\n")


def resolve_alias_options(alias_or_model_id):
"""
Resolve an alias to its model and options, if it exists.
Returns None if not an alias, or a dict with 'model' and 'options' keys.
"""
path = user_dir() / "aliases.json"
if not path.exists():
return None
try:
current = json.loads(path.read_text())
except json.decoder.JSONDecodeError:
return None

if alias_or_model_id not in current:
return None

alias_value = current[alias_or_model_id]

# Check if it's the new format (dict with model and options)
if isinstance(alias_value, dict) and "model" in alias_value:
return alias_value

# Otherwise it's the old format (just a string), not relevant for options
return None


def remove_alias(alias):
"""
Remove an alias.
Expand Down
43 changes: 39 additions & 4 deletions llm/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
get_models_with_aliases,
user_dir,
set_alias,
set_alias_with_options,
resolve_alias_options,
set_default_model,
set_default_embedding_model,
remove_alias,
Expand Down Expand Up @@ -742,6 +744,16 @@ def read_prompt():
else:
model_id = get_default_model()

# Resolve alias with options if applicable
alias_options = resolve_alias_options(model_id)
if alias_options:
model_id = alias_options["model"]
# Merge alias options with command-line options, giving priority to command-line
merged_options = alias_options.get("options", {}).copy()
if options:
merged_options.update(dict(options))
options = list(merged_options.items())

# Now resolve the model
try:
if async_:
Expand Down Expand Up @@ -1070,6 +1082,16 @@ def chat(
else:
model_id = get_default_model()

# Resolve alias with options if applicable
alias_options = resolve_alias_options(model_id)
if alias_options:
model_id = alias_options["model"]
# Merge alias options with command-line options, giving priority to command-line
merged_options = alias_options.get("options", {}).copy()
if options:
merged_options.update(dict(options))
options = list(merged_options.items())

# Now resolve the model
try:
model = get_model(model_id)
Expand Down Expand Up @@ -2679,7 +2701,15 @@ def aliases_list(json_):
multiple=True,
help="Set alias for model matching these strings",
)
def aliases_set(alias, model_id, query):
@click.option(
"options",
"-o",
"--option",
type=(str, str),
multiple=True,
help="Options to include with the alias",
)
def aliases_set(alias, model_id, query, options):
"""
Set an alias for a model

Expand Down Expand Up @@ -2710,14 +2740,19 @@ def aliases_set(alias, model_id, query):
"No model found matching query: " + ", ".join(query)
)
model_id = found.model.model_id
set_alias(alias, model_id)
if options:
set_alias_with_options(alias, model_id, dict(options))
else:
set_alias(alias, model_id)
click.echo(
f"Alias '{alias}' set to model '{model_id}'",
err=True,
)
else:
set_alias(alias, model_id)

if options:
set_alias_with_options(alias, model_id, dict(options))
else:
set_alias(alias, model_id)

@aliases.command(name="remove")
@click.argument("alias")
Expand Down
Loading