Alchemist | Documentation
Bring Your Own tools

Bring Your Own Tools

Alchemist allows you to add your own tools to the agent. This is done by creating a local directory with Python files including additional tool definitions. The directory is specified in the Alchemist settings.

Tool Specification

To add your own tools, you need to create a Python file with the tool definition. Every tool requires specification of three classes for input parameters, output parameters, and tool itself. The three classes will inherit from ToolParameters, ToolResult and Tool respectively.

For a new tool called NewToolExample the definition of the tool parameter class may look like this:

class NewToolExampleParams(ToolParameters):
    """
    Defines the input parameters for the NewToolExample tool.

    :param param1: Description of parameter 1
    :param param2: Description of parameter 2
    """
    param1: str = Field(..., description="Description of parameter 1")
    param2: str = Field(..., description="Description of parameter 2")    

Then the output parameters class:

class NewToolExampleResult(ToolResult):
    """
    Defines the output result for the NewToolExample tool.
    """
    result: str = Field(default="", description="Description of the result")
    status: str = Field(default="OK", description="Indicates if the tool was successful")
    message: str = Field(default="", description="Message regarding the tool execution")

    def __str__(self) -> str:
        return (f"{{ 'result': '{self.result}', 'status': '{self.status}', "
                f"'message': '{self.message}' }}")

And finally the tool class:

class NewToolExample(Tool):
    """
    Tool to perform a specific task.
    """

    name = "NewToolExample"
    description = "Performs a specific task. This description is provided to the model."
                   "First write QMD file and use this tool to render it.")
    example = ("{ 'name': 'NewToolExample', 'parameters': "
               "{ 'param1': 'value1', 'param2': 'value2' } }")

    def run(self, parameters: NewToolExampleParams) -> NewToolExampleResult:  # type: ignore
        """
        Execute the tool with the provided parameters.

        :param parameters: NewToolExampleParams object with the input parameters
        :return: NewToolExampleResult indicating success or failure
        """
        # Implement the tool logic here
        result = f"Executed with param1={parameters.param1} and param2={parameters.param2}"
        return NewToolExampleResult(result=result)

In the example above, the run method is where you implement the logic of your tool. The method takes an instance of the NewToolExampleParams class as input and returns an instance of the NewToolExampleResult class.

Class NewToolExample(Tool) includes three attributes:

  • name - Name of the tool. This name is used in the agent flow and used by the agent to invoke the tool.
  • description - Description of the tool. This description is provided to the model.
  • example - Example of how to call the tool. This example is provided to the model.

Every tool must have a unique name. If tool requires additional Python packages installed in for example virtual environment, the tool will need to add this path using sys.path.append() function. The PYTHONPATH is not used. Some tools may require additional entries into PATH environmental variable.

Enable User provided Tools

To enable user provided tools, you need to select Enable User Tools option and set the following parameters in Agent Tools section of the Alchemist settings:

Enable User Provided Tools
Enable user provided tools in Alchemist Settings.
  • User Tool Directory - Local directory with Python files including additional tool definitions.
  • Additional System Path (PATH) Entries - Additional entries into PATH environmental variables defining additional folders with binary files. Change of this value requires full restart of Alchemist to take an effect.
  • User Tools - Names of tools as defined in the Python definitions. Comma separated string including full names, e.g. QuartoRender, ArchitectureDiagram, ...

The user provided tools will be available in the agent flow in the same way as internally provided tools.

Example tool

Here is an example of a tool that renders Quarto documents. The tool takes the path to the Quarto document and the output directory as input parameters. The output of the tool is a rendered file.

"""
This module contains the QuartoRender class, which is a tool for rendering Quarto documents.
"""

import subprocess
from pathlib import Path

from pydantic import Field
from loguru import logger
from atomic.core.toolbox.tool import Tool, ToolParameters, ToolResult


class QuartoRenderParams(ToolParameters):
    """
    Defines the input parameters for the QuartoRender tool.

    :param source_file: Path to the source file to render
    :param output_format: Desired output format (html, pdf, pptx)
    """
    source_file: str = Field(..., description="Path to the source file to render.")
    output_format: str = Field(..., description="Desired output format (html, pdf, pptx).")


class QuartoRenderResult(ToolResult):
    """
    Defines the output result for the QuartoRender tool.
    """
    filename: str = Field(default="", description="Name of the rendered file.")
    status: str = Field(default="OK", description="Indicates if the rendering was successful.")
    message: str = Field(default="", description="Message regarding the rendering process.")

    def __str__(self) -> str:
        return (f"{{ 'FileWritten': '{self.filename}', 'status': '{self.status}', "
                f"'message': '{self.message}' }}")

class QuartoRender(Tool):
    """
    Tool to render documents using Quarto into HTML, PDF, or PPTX formats.
    """

    name = "QuartoRender"
    description = ("Renders documents using Quarto into HTML, PDF, or PPTX formats. "
                   "First write QMD file and use this tool to render it.")
    example = ("{ 'name': 'QuartoRender', 'parameters': "
    "{ 'source_file': './stock_market_analysis.qmd', 'output_format': 'pptx' } }")

    def run(self, parameters: QuartoRenderParams) -> QuartoRenderResult:
        """
        Render the specified source file into the desired format using Quarto.

        :param parameters: QuartoRenderParams object with the source file and output format
        :return: QuartoRenderResult indicating success or failure
        """
        source_file = Path(parameters.source_file)
        output_format = parameters.output_format.lower()

        # Validate output format
        if output_format not in ['html', 'pdf', 'pptx']:
            message = "Unsupported output format. Choose from 'html', 'pdf', 'pptx'."
            logger.error(message)
            return QuartoRenderResult(status='ERROR', message=message)

        try:
            # Construct the Quarto command
            command = ["quarto", "render", str(source_file), "--to", output_format]

            # Execute the command
            subprocess.run(command, check=True)
            logger.info(f"Document rendered successfully to {output_format} format.")

            outfile = source_file.with_suffix(f".{output_format}")

            return QuartoRenderResult(
                filename=str(outfile),
                status='OK',
                message=f"Document rendered to {output_format} format."
            )

        except subprocess.CalledProcessError as e:
            logger.error(f"Error rendering document: {e}")
            return QuartoRenderResult(status='ERROR', message=str(e))