-
-
Notifications
You must be signed in to change notification settings - Fork 28
How To Write Plugins for MILES
This guide provides detailed instructions on writing and documenting plugins for the voice assistant, Miles, focusing on the structure and formatting required for plugin.py
.
A docstring is a string literal specified in source code that is used, like a comment, to document a specific segment of code. Unlike conventional comments, docstrings are retained throughout the runtime of the program. This allows the programmer to inspect these comments at run time, for instance, to generate documentation.
In Python, a docstring is placed in the immediate line after the definition of a function, class, module, or method. It begins and ends with triple quotes ("""
), allowing for multiline comments.
def example_function(param1, param2):
"""
This is a docstring for documentation of the example_function.
Parameters:
param1 (int): Description of param1.
param2 (str): Description of param2.
"""
pass
Please note that this is not correct formatting for Miles, this is just an explanation
Parameters are the names used in a function definition to specify the kinds of arguments that the function can accept. In the context of plugins for Miles, parameters define the data you can pass to the function to influence its behavior or output.
When calling a function, you can provide values (arguments) for its parameters. This allows the same function to operate on different data, providing different results based on the inputs.
def greet(name, message="Hello"):
"""
Greets a person with a message.
Parameters:
name (str): The name of the person.
message (str): (Optional) A custom message. Defaults to "Hello".
"""
print(f"{message}, {name}!")
Please note that this is not correct formatting for Miles, this is just an explanation
This function can be used inside or outside of the voice assistant. When used outside, you directly provide arguments for its parameters:
greet("Alice", "Welcome") # Outputs: Welcome, Alice!
When integrated with Miles, the voice assistant might dynamically choose the parameters for you based on the context or specific commands given by the user.
Ensure your plugin functions adhere to these guidelines for seamless integration:
- All functions must reside in the file named
plugin.py
that is already in the Miles-V2 folder. - Each function must have a well-defined task.
- Properly document each function with a docstring detailing its purpose, parameters, if a parameter is required, and whether or not it's a main function.
Each function's docstring must follow a specific format for Miles to parse and understand its capabilities:
- Description: Clearly state the function's purpose at the beginning.
- Parameter Descriptions: For each parameter, describe its purpose and whether it is optional.
- Required Parameters: Explicitly list all required parameters.
- Main Function: "Yes" if you want it exposed as a tool. Don't include it if not (like when a helper function is needed).
def fetch_weather(city: str, metric: bool = True):
"""
Description: Fetches and returns the current weather for a specified city.
Parameter Description for {city}: The name of the city for which to fetch the weather.
Parameter Description for {metric}: (Optional) Whether to use metric units. Defaults to True.
Required Parameters: {city}
Main Function: Yes
"""
print(f"[Miles is finding the weather in {city}…]") # You need to include a print statement within brackets
# starting with 'Miles is' for the app to parse the output as an action.
# Typically the logic for the weather API would then go here, just like in a normal function.
return {"data": "Weather data here"}
After that code is sent through the tool generator, it pops out JSON like this:
[
{
"type": "function",
"function": {
"name": "fetch_weather",
"description": "Fetches and returns the current weather for a specified city.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The name of the city for which to fetch the weather."
},
"metric": {
"type": "boolean",
"description": "(Optional) Whether to use metric units. Defaults to True."
}
},
"required": [
"city"
]
}
}
}
]
If you don't enter the docstring formatting correctly, like in this case where I purposely didn't include a double new line after the parameter description, then you might see this happen:
def calculate_discount(price: float, discount_rate: float, is_member: bool = False):
"""
Description: Calculates the final price after applying a discount rate. Members receive an additional 5% discount.
Parameter Description for {price}: The original price of the item.
Parameter Description for {discount_rate}: The discount rate to apply to the original price. Represented as a decimal (e.g., 0.2 for 20% off).
Parameter Description for {is_member}: (Optional) Indicates if the customer is a member. Members receive an additional 5% discount. Defaults to False.
Required Parameters: {price, discount_rate}
Main Function: Yes
"""
print(f"[Miles is calculating discount rate for {price}…]")
member_discount = 0.05 if is_member else 0
final_discount = discount_rate + member_discount
final_price = price * (1 - final_discount)
return {"final_price": final_price}
After running it through the tool generator, you get this:
Notice that the descriptions for the parameters {price}
and {discount_rate}
aren't filled out correctly, this is because the tool generator can't parse the docstring if it doesn't have double spaced new lines between each entry.
[
{
"type": "function",
"function": {
"name": "calculate_discount",
"description": "Calculates the final price after applying a discount rate. Members receive an additional 5% discount.",
"parameters": {
"type": "object",
"properties": {
"price": {
"type": "number",
"description": ""
},
"discount_rate": {
"type": "number",
"description": ""
},
"is_member": {
"type": "boolean",
"description": "(Optional) Indicates if the customer is a member. Members receive an additional 5% discount. Defaults to False."
}
},
"required": [
"price",
"discount_rate"
]
}
}
}
]
Helper functions are designed to perform common tasks that support your main tool functions. They are not directly exposed as tools but are crucial for reducing code duplication and enhancing readability.
def format_response(data):
"""
Formats numerical result data into a more readable string format.
"""
return f"Calculated Result: {data}"
Main tool functions are the primary actions or tools you wish to expose. Each should have a clear purpose and a well-documented docstring.
def calculate_sum(a: int, b: int):
"""
Description: Calculates the sum of two given integers, `a` and `b`, and returns the result in a human-readable format. Ideal for performing quick arithmetic operations.
Parameter Description for {a}: The first integer to add. It is a required parameter.
Parameter Description for {b}: The second integer to add. It is a required parameter.
Required Parameters: {a, b}
Main Function: Yes
"""
result = a + b
return format_response(result)
def multiply_numbers(x: int, y: int):
"""
Description: Multiplies two given integers, `x` and `y`, and formats the product for easy reading. Suited for calculations needing product results.
Parameter Description for {x}: The multiplicand. It is a required parameter for the multiplication.
Parameter Description for {y}: The multiplier. It is also a required parameter for the operation.
Required Parameters: {x, y}
Main Function: Yes
"""
result = x * y
return format_response(result)
[
{
"type": "function",
"function": {
"name": "calculate_sum",
"description": "Calculates the sum of two given integers, `a` and `b`, and returns the result in a human-readable format. Ideal for performing quick arithmetic operations.",
"parameters": {
"type": "object",
"properties": {
"a": {
"type": "integer",
"description": "The first integer to add. It is a required parameter."
},
"b": {
"type": "integer",
"description": "The second integer to add. It is a required parameter."
}
},
"required": [
"a",
"b"
]
}
}
},
{
"type": "function",
"function": {
"name": "multiply_numbers",
"description": "Multiplies two given integers, `x` and `y`, and formats the product for easy reading. Suited for calculations needing product results.",
"parameters": {
"type": "object",
"properties": {
"x": {
"type": "integer",
"description": "The multiplicand. It is a required parameter for the multiplication."
},
"y": {
"type": "integer",
"description": "The multiplier. It is also a required parameter for the operation."
}
},
"required": [
"x",
"y"
]
}
}
}
]
When you write a python function with the correct docstring format into the plugin.py
file, several things happen:
-
Nothing actually happens until you run Miles with
npm start
. -
Once Miles is running, the
plugin.py
script is parsed for the docstring. -
Details about the function like the name, and type of parameters are parsed.
-
These details are then formatted into JSON and sent to the
plugin_tool_list.json
file. -
Then inside the main process, the details about the functions are extracted and set as variables to be appended to the main tools.
-
So when Miles sees his tools, there is no difference at all between the stock tools, and any tool you add. They are all combined together as one.