310 lines
16 KiB
Markdown
310 lines
16 KiB
Markdown
# Custom Bash-Based Tools
|
|
Loki supports tools written in Bash. However, they must be written in a special format with special annotations in order
|
|
for Loki to be able to properly parse and utilize them. This formatting ensures that each Bash script is
|
|
self-describing, and formatted in such a way that Loki can anticipate how to execute it and what parameters to pass to
|
|
it. This standardization also lets Loki compile the script into a JSON schema that can be used to inform the LLM about
|
|
how to use the tool.
|
|
|
|
Each Bash-based tool must follow a specific structure in order for Loki to be able to properly compile and execute it:
|
|
|
|
* The tool must be a Bash script with a `.sh` file extension.
|
|
* The script must have the following comments:
|
|
* `# @describe ...` comment at the top that describes the tool.
|
|
* `# @env LLM_OUTPUT=/dev/stdout The output path` comment to describe the `LLM_OUTPUT` environment variable. This
|
|
syntax in particular assigns `/dev/stdout` as the default value for `LLM_OUTPUT`, so that if it's not set by Loki,
|
|
the script will still function properly.
|
|
* `# @option --option <value> An example option` comments to define each option that the tool accepts.
|
|
* Use `--flag` syntax for boolean flags.
|
|
* Use `--option <value>` syntax for options that accept a value.
|
|
* Use `--option <value1,value2>` syntax for options that accept multiple values (i.e. arrays).
|
|
* The script must have a `main` function
|
|
* The `main` function must redirect the return value to the `>> "$LLM_OUTPUT"` environment variable.
|
|
* This is necessary because Loki relies on the `$LLM_OUTPUT` environment variable to capture the output of the tool.
|
|
|
|
Essentially, you can think of the Bash-based tool script as just a normal Bash script that uses special comments to
|
|
define a CLI.
|
|
* The `# @env LLM_OUTPUT=/dev/stdout` comment to define the `$LLM_OUTPUT` environment variable (good practice)
|
|
* A `# @describe`
|
|
* And a `main` function that writes to `$LLM_OUTPUT`
|
|
|
|
The following section explains how you can add parameters to your bash functions and how to test out your scripts.
|
|
|
|
## Quick Links:
|
|
<!--toc:start-->
|
|
- [Loki Bash Tools Syntax](#loki-bash-tools-syntax)
|
|
- [Metadata](#metadata)
|
|
- [Environment Variables](#environment-variables)
|
|
- [Arguments](#arguments)
|
|
- [Flags](#flags)
|
|
- [Options](#options)
|
|
- [Subcommands (Agents only)](#subcommands-agents-only)
|
|
- [Execute and Test Your Bash Tools](#execute-and-test-your-bash-tools)
|
|
- [Example](#example)
|
|
- [Prompt Helpers](#prompt-helpers)
|
|
<!--toc:end-->
|
|
|
|
---
|
|
|
|
## Loki Bash Tools Syntax
|
|
Loki Bash tools work via `@___` annotations that describe specific functionality of a script. The following reference
|
|
explains the general syntax of these annotations and how to use them to create a CLI that Loki can recognize.
|
|
|
|
Refer to the [Execute and Test Your Bash Tools](#execute-and-test-your-bash-tools) section to learn how to test out your Bash tools
|
|
without needing to go through Loki itself.
|
|
|
|
It's important to note that any functions prefixed with `_` are not sent to the LLM, so they will be invisible to the
|
|
LLM at runtime.
|
|
|
|
### Metadata:
|
|
You can define different metadata about your script to help Loki understand its dependencies and purpose.
|
|
|
|
```bash
|
|
# Use the `@meta require-tools` annotation to specify any external tools that your script depends on.
|
|
# @meta require-tools jq,yq
|
|
|
|
# Use the `@describe` annotation to describe the purpose of the script.
|
|
# @describe A tool to interact with things
|
|
```
|
|
|
|
### Environment Variables:
|
|
```bash
|
|
###########################
|
|
## Environment Variables ##
|
|
###########################
|
|
|
|
# Use `@env` to define environment variables that the script uses.
|
|
# @env LLM_OUTPUT=/dev/stdout The output path, with a default value of '/dev/stdout' if not set.
|
|
# @env OPTIONAL An optional environment variable
|
|
# @env REQUIRED! A required environment variable
|
|
# @env DEFAULT_VALUE=default An environment variable with a default value if unset.
|
|
# @env DEFAULT_FROM_FN=`_default_env_fn` An environment variable with a default value calculated from a function if unset.
|
|
# @env CHOICE[even|odd] An environment variable that, if set, must be set to either `even` or `odd`
|
|
# @env CHOICE_WITH_DEFAULT[=even|odd] An environment variable that, if set, must be set to either `even` or `odd`, and defaults to `even` when unset
|
|
# @env CHOICE_FROM_FN[`_choice_env_fn`] An environment variable that, if set, must be set to one of the values returned by the `_choice_fn` function.
|
|
|
|
# Example variable usage:
|
|
export CHOICE=even
|
|
# ./script.sh
|
|
main() {
|
|
[[ $CHOICE == "even" ]] || { echo "The value of the 'CHOICE' env var is not 'even'" >> "$LLM_OUTPUT" && exit 1 }
|
|
}
|
|
|
|
# Loki does not pass functions prefixed with `_` to the LLM, so these are essentially `private` functions
|
|
_default_env_fn() {
|
|
echo "calculated default env value"
|
|
}
|
|
|
|
# Loki does not pass functions prefixed with `_` to the LLM, so these are essentially `private` functions
|
|
_choice_env_fn() {
|
|
echo even
|
|
echo odd
|
|
}
|
|
```
|
|
|
|
### Arguments:
|
|
When referencing an argument defined via the `@arg` annotation, you can access its value using the `argc_<argument_name>` variable that
|
|
is created at runtime.
|
|
|
|
```bash
|
|
###############
|
|
## Arguments ##
|
|
###############
|
|
|
|
# Use `@arg` To define positional arguments for your script.
|
|
# To reference an argument within your script, use the `argc_<argument_name>` variable.
|
|
# @arg optional Optional argument
|
|
# @arg required! Required argument
|
|
# @arg multi_value* An argument that accepts multiple values (e.g. './script.sh one two three')
|
|
# @arg multi_value_required+ An argument that is required and accepts multiple values
|
|
# @arg value_notated <VALUE> An argument that explicitly specifies the name for documentation (e.g. Usage: ./script.sh [VALUE])
|
|
# @arg default=default An argument with a default value if unset
|
|
# @arg default_from_fn=`_default_arg_fn` An argument with a default value calculated from a function if unset
|
|
# @arg choice[even|odd] An argument that, if set, must be set to either `even` or `odd`
|
|
# @arg required_choice+[even|odd] An required argument that must be set to either `even` or `odd`
|
|
# @arg default_choice[=even|odd] An argument that if unset defaults to 'even', but if set must be either `even` or `odd`
|
|
# @arg multi_value_choice*[even|odd] An argument that, if set, must be set to either `even` or `odd`, and accepts multiple values
|
|
# @arg choice_fn[`_choice_arg_fn`] An argument that, if set, must be set to one of the values returned by the `_choice_arg_fn` function.
|
|
# @arg choice_fn_no_valid[?`_choice_arg_fn`] An argument that, if set, can be set to one of the values returned by the `_choice_arg_fn` function,
|
|
# but does not validate the value.
|
|
# @arg multi_choice_fn*[`_choice_arg_fn`] An argument that, if set, must be set to one of the values returned by the `_choice_arg_fn` function,
|
|
# and accepts multiple values.
|
|
# @arg multi_choice_comma_fn*,[`_choice_arg_fn`] An argument that, if set, must be set to one of the values returned by the `_choice_arg_fn` function,
|
|
# and accepts multiple values in the form of a comma-separated list
|
|
# @arg capture_arg~ An argument that captures all remaining args passed to the script
|
|
|
|
# Example usage 1: ./script.sh something_required
|
|
main() {
|
|
[[ $argc_required == "something_required" ]] || { echo "The value of the 'required' arg is not 'something_required'" >> "$LLM_OUTPUT" && exit 1 }
|
|
}
|
|
|
|
# Example usage 2: ./script.sh this is a test
|
|
main() {
|
|
[[ "${argc_multi_value[*]}" == "this is a test" ]] || { echo "The value of the 'multi_value' arg is not 'this is a test'" >> "$LLM_OUTPUT" && exit 1 }
|
|
}
|
|
|
|
|
|
# Loki does not pass functions prefixed with `_` to the LLM, so these are essentially `private` functions
|
|
_default_arg_fn() {
|
|
echo "default arg value"
|
|
}
|
|
|
|
# Loki does not pass functions prefixed with `_` to the LLM, so these are essentially `private` functions
|
|
_choice_arg_fn() {
|
|
echo even
|
|
echo odd
|
|
}
|
|
```
|
|
|
|
### Flags:
|
|
To access the value of a flag defined via the `@flag` annotation, you can check the value of the `argc_<flag_name>` variable.
|
|
|
|
```bash
|
|
###########
|
|
## Flags ##
|
|
###########
|
|
|
|
# Use `@flag` to define boolean flags for your script
|
|
# To reference a flag within your script, use the `argc_<argument_name>` variable.
|
|
# @flag --bool A boolean flag with only a long option
|
|
# @flag -b --bool A boolean flag with a short and long option
|
|
# @flag -b A boolean flag with only a short option
|
|
# @flag --multi* A boolean flag that can be used multiple times (e.g. '--multi --multi' will return '2')
|
|
|
|
# Example usage 1: ./script.sh --bool
|
|
main() {
|
|
[[ $argc_bool == "1" ]] || { echo "The value of the 'bool' flag is not '1'" >> "$LLM_OUTPUT" && exit 1 }
|
|
}
|
|
|
|
# Example usage 2: ./script.sh --multi --multi
|
|
main() {
|
|
[[ $argc_multi == "2" ]] || { echo "The value of the 'multi' flag is not 2" >> "$LLM_OUTPUT" && exit 1 }
|
|
}
|
|
```
|
|
|
|
### Options:
|
|
To access the value of an option defined via the `@option` annotation, you can check the value of the `argc_<option_name>` variable.
|
|
|
|
```bash
|
|
#############
|
|
## Options ##
|
|
#############
|
|
|
|
# Use `@option` to define flags that accept values
|
|
# To reference an option within your script, use the `argc_<argument_name>` variable.
|
|
# @option --option An option that accepts a value with only a long flag
|
|
# @option -o --option An option that accepts a value with both a short and long flag
|
|
# @option -o An option that accepts a value with only a short flag
|
|
# @option --required A required option that accepts a value
|
|
# @option --multi* An option that accepts multiple values
|
|
# @option --required-multi+ An option that accepts multiple values and is required
|
|
# @option --multi-comma*, An option that accepts multiple values in the form of a comma-separated list
|
|
# @option --value <VALUE> An option that explicitly specifies the name for documentation (e.g. Usage: ./script.sh --value [VALUE])
|
|
# @option --two-args <SRC> <DEST> An option that accepts two arguments and explicitly names them for documentation
|
|
# (e.g. Usage: ./script.sh --two-args [SRC] [DEST])
|
|
# @option --unlimited-args <SRC> <DEST+> An option that accepts an unlimited number of arguments and explicitly names them for documentation
|
|
# (e.g. Usage: ./script.sh --unlimited-args [SRC] [DEST ...])
|
|
# @option --default=default An option that has a default value if unset
|
|
# @option --default-from-fn=`_default_opt_fn` An option that has a default value calculated from a function if unset
|
|
# @option --choice[even|odd] An option that, if set, must be set to either `even` or `odd`
|
|
# @option --choice-default[=even|odd] An option that, if unset, defaults to `even`, but if set must be either `even` or `odd`
|
|
# @option --choice-multi*[even|odd] An option that, if set, must be set to either `even` or `odd`, and can be specified multiple times
|
|
# (e.g. ./script.sh --choice-multi even --choice-multi odd)
|
|
# @option --required-choice-multi+[even|odd] A required option that, must be set to either `even` or `odd`, and can be specified multiple times
|
|
# @option --choice-fn[`_choice_opt_fn`] An option that, if set, must be set to one of the values returned by the `_choice_opt_fn` function.`
|
|
# @option --choice-fn-no-valid[?`_choice_opt_fn`] An option that, if set, can be set to one of the values returned by the `_choice_opt_fn` function, with no validation
|
|
# @option --choice-multi-fn*[`_choice_opt_fn`] An option that, if set, must be set to one of the values returned by the `_choice_opt_fn` function,
|
|
# and can be specified multiple times
|
|
# @option --choice-multi-comma*,[`_choice_opt_fn`] An option that, if set, must be set to one of the values returned by the `_choice_opt_fn` function,
|
|
# and is specified as a comma-separated list
|
|
# @option --capture~ An option that captures all remaining arguments passed to the script
|
|
|
|
# Example usage 1: ./script.sh --option some_value
|
|
main() {
|
|
[[ $argc_option == "some_value" ]] || { echo "The value of the 'option' option is not 'some_value'" >> "$LLM_OUTPUT" && exit 1 }
|
|
}
|
|
|
|
# Example usage 2: ./script.sh --multi value1 --multi value2
|
|
main() {
|
|
[[ "${argc_multi[*]}" == "value1 value2" ]] || { echo "The value of the 'multi' option is not 'value1 value2'" >> "$LLM_OUTPUT" && exit 1 }
|
|
}
|
|
|
|
|
|
# Loki does not pass functions prefixed with `_` to the LLM, so these are essentially `private` functions
|
|
_default_opt_fn() {
|
|
echo "calculated default option value"
|
|
}
|
|
|
|
# Loki does not pass functions prefixed with `_` to the LLM, so these are essentially `private` functions
|
|
_choice_opt_fn() {
|
|
echo even
|
|
echo odd
|
|
}
|
|
```
|
|
|
|
### Subcommands (Agents only):
|
|
By default, if no `@cmd` annotations are defined, the script's `main` function is treated as the default command.
|
|
However, for agents, there can be many functions defined in one file, and thus it is useful to create subcommands
|
|
to organize your agent's tools.
|
|
|
|
```bash
|
|
#################
|
|
## Subcommands ##
|
|
#################
|
|
|
|
# Use the `@cmd` annotation to define subcommands for your script.
|
|
# @cmd List all files
|
|
list() {
|
|
ls -la >> "$LLM_OUTPUT"
|
|
}
|
|
|
|
# @cmd Output the contents of the specified file
|
|
# @arg file! The file to output
|
|
cat() {
|
|
cat "$argc_file" >> "$LLM_OUTPUT"
|
|
}
|
|
|
|
# Example usage 1: ./script.sh cat myfile.txt
|
|
```
|
|
|
|
## Execute and Test Your Bash Tools
|
|
Your bash tools are just normal bash scripts stored in the `functions/tools` directory. So you can execute and test them
|
|
directly by first having Loki compile them so all this syntactic sugar means something.
|
|
|
|
This is achieved via the `loki --build-tools` command.
|
|
|
|
### Example
|
|
Suppose we want to execute the `functions/tools/get_current_time.sh` script for testing.
|
|
|
|
We'd first make sure the script is visible in all contexts by ensuring it's in the `visible_tools` array in your global
|
|
`config.yaml` file. This ensures Loki builds the tool so it's ready to use in any context.
|
|
|
|
You can find the location of your global `config.yaml` file with the following command:
|
|
|
|
```shell
|
|
loki --info | grep 'config_file' | awk '{print $2}'
|
|
```
|
|
|
|
Then, we can instruct Loki to build the script so we can test it out:
|
|
|
|
```shell
|
|
loki --build-tools
|
|
```
|
|
|
|
This will add additional boilerplate to the top of the script so that it can be executed directly.
|
|
|
|
Finally, we can now execute the script:
|
|
|
|
```bash
|
|
$ ./get_current_time.sh
|
|
Fri Oct 24 05:55:04 PM MDT 2025
|
|
```
|
|
|
|
## Prompt Helpers
|
|
It's often useful to create interactive prompts for our bash tools so that our tools can get input from
|
|
users.
|
|
|
|
To accommodate this, Loki provides a set of prompt helper functions that can be referenced and used within your Bash
|
|
tools.
|
|
|
|
For more information, refer to the [Bash Prompt Helpers documentation](BASH-PROMPT-HELPERS.md).
|