Skip to content

System Utilities

Antonio Fin edited this page Nov 10, 2024 · 2 revisions

In the LL-Mesh platform, system utilities are essential for the seamless operation of all tools and web applications. These services include logging, configuration management, and tool management. Each plays a critical role in ensuring consistency, ease of use, and flexibility across the platform.

For more detailed information, please refer to the following sections:

Logging Service

The logging service provides a standardized and configurable logger that can be used by all tools and web applications within the platform. This ensures that logs are consistent, easy to read, and can be configured according to the needs of the specific application.

  • Consistent Logging: All tools use a centralized logging system, ensuring uniformity across the platform.
  • Configurable: The logging service allows developers to customize logging levels, formats, and outputs, making it adaptable to different environments and use cases.

Here's an example of how you can use the Logger class to set up and utilize logging in an application:

from athon.system import Logger

# Configuration dictionary for the logger
config = {
    "name": "MyApplicationLogger",
    "level": "INFO",
    "log_file": "my_app.log",
    "log_format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    "max_bytes": 10485760,  # 10 MB
    "backup_count": 3  # Keep 3 backup log files
}

# Initialize the logger with the configuration
logger_instance = Logger(config)

# Retrieve the logger
logger = logger_instance.get_logger()

# Log various messages
logger.info("This is an info message.")
logger.debug("This is a debug message.")
logger.warning("This is a warning message.")
logger.error("This is an error message.")
logger.critical("This is a critical message.")

Configuration Service

The configuration service allows tools and web applications to read and utilize configuration files in a declarative manner. This service simplifies the management of settings and parameters across the platform, ensuring that all tools can be easily configured without the need for hardcoding values.

  • Declarative Logic: Configurations are defined in easy-to-read files, allowing users to declare settings without altering the code.
  • Standardized Configuration Handling: This service standardizes the way configurations are managed across multiple tools and services, ensuring consistency and ease of use, while allowing each tool to maintain its own configuration file.

To illustrate how the Config class is used with a configuration file like the tool_api example, let's break down the example configuration file and discuss the placeholder replacement functionality.

Example Configuration File (examples/tool_api)

webapp:
  ip: 0.0.0.0
  port: 5003
  ssh_cert: adhoc

tool:
  name: TemperatureFinder
  function: $FUNCTION{temperature_finder}
  arguments:
    - name: latitude
      type: float
      description: $PROMPT{field_latitude_description}
      default : null
      fields:
        - ge: -90.0  # Greater than or equal to -90
        - le: 90.0  # Less than or equal to 90      
    - name: longitude
      type: float
      description: $PROMPT{field_longitude_description}
      default : null
      fields:
        - ge: -180.0  # Greater than or equal to -180
        - le: 180.0  # Less than or equal to 180
  description: $PROMPT{tool_description}
  return_direct: false
  interface:
    fields:
      - name: latitude
        label: "Insert Latitude"
        type: number
      - name: longitude
        label: "Insert Longitude"
        type: number

function:
  meteo_api: https://api.open-meteo.com/v1/forecast

prompts:
  type: JinjaTemplate
  environment: examples/tool_api/prompts
  templates:
    tool_description: tool_description.txt
    field_latitude_description: field_latitude_description.txt
    field_longitude_description: field_longitude_description.txt

logger:
  name: TEMPERATURE_FINDER
  log_file: examples/tool_api/logs/temperature_finder.log
  level: DEBUG

Placeholder Replacement

The Config class is designed to dynamically replace placeholders in the configuration file with actual values during runtime. The placeholders follow a specific syntax and are replaced based on the context in which they appear:

  1. $FUNCTION{}:

    • Placeholder: $FUNCTION{temperature_finder}
    • Replaced With: The actual function name or reference. This allows dynamic binding of function names in the configuration file.
  2. $PROMPT{}:

    • Placeholder: $PROMPT{tool_description}
    • Replaced With: The content of the prompt file specified in the prompts section. The PromptRender class is used to load and render these prompts, which can be useful for providing dynamic descriptions or messages.
  3. $ENV{}:

    • This placeholder (not shown in the example) would be replaced by environment variables. For example, $ENV{DATABASE_URL} would be replaced by the value of the DATABASE_URL environment variable.
  4. $TOOL{}:

    • This placeholder (also not shown in the example) is used for resolving tool instances by name. This could be useful when dynamically loading tools based on configuration.

Example Usage in Code

Here's an example of how you might use the Config class with this configuration file:

from athon.system import Config

# Initialize the configuration
config = Config(config_file="path/to/tool_api/config.yaml")

# Access the configuration settings
settings = config.get_settings()

# Example: Access web app settings
webapp_settings = settings.get('webapp')
print(f"WebApp IP: {webapp_settings['ip']}, Port: {webapp_settings['port']}")

# Example: Access tool description
tool_description = settings['tool']['description']
print(f"Tool Description: {tool_description}")

# Example: Access logger settings
logger_config = settings['logger']
print(f"Logger Name: {logger_config['name']}, Log File: {logger_config['log_file']}")

Tool Management Services

Tool management services are a crucial part of the LL-Mesh platform, providing the capabilities to transform code functions into fully-fledged LL-Mesh tools and manage them across the platform. These services operate in two key phases:

  1. Discovery Phase: During this phase, the server retrieves the Tool Manifest from the client, which contains the tool's description, its function, the function arguments, and optionally an interface schema. The Tool Manifest defines how the tool is structured and provides essential metadata such as the tool's name, function, arguments, and interface. For example:

    tool:
      name: TemperatureFinder
      function: $FUNCTION{temperature_finder}
      arguments:
        - name: latitude
          type: float
          description: $PROMPT{field_latitude_description}
          default: null
          fields:
            - ge: -90.0  # Greater than or equal to -90
            - le: 90.0  # Less than or equal to 90      
        - name: longitude
          type: float
          description: $PROMPT{field_longitude_description}
          default: null
          fields:
            - ge: -180.0  # Greater than or equal to -180
            - le: 180.0  # Less than or equal to 180
      description: $PROMPT{tool_description}
      return_direct: false
      interface:
        fields:
          - name: latitude
            label: "Insert Latitude"
            type: number
          - name: longitude
            label: "Insert Longitude"
            type: number
  2. Execution Phase: In this phase, the server can invoke the tool using the arguments specified in the manifest. This can be done either through a chat interface or directly via the provided user interface, if available. The tool's execution is guided by the metadata defined during the discovery phase, ensuring that it operates as intended and returns the expected results.

tool

Tool Client Service

The Tool Client Service allows developers to transform any code function into an LL-Mesh tool by simply applying a decorator. This service abstracts the complexities of tool integration, enabling developers to quickly convert their functions into reusable tools within the LL-Mesh ecosystem.

  • Decorator-Based: Convert functions into tools by applying a simple decorator.
  • Seamless Integration: Once decorated, functions are fully integrated into the LL-Mesh platform, ready to be used across different applications and services.

Here's an example of how to use the AthonTool decorator to create a simple tool that greets a user. This decorator not only enhances the function by providing additional logic, such as logging and configuration management, but also automatically generates a manifest and creates an endpoint for the tool when used in a web application.

from athon.system import AthonTool

# Example configuration and logger (these should be defined according to your tool's requirements)
config = {
    "greetings": "Hello World! Welcome, "
}
logger = None  # Replace with a properly configured logger

@AthonTool(config, logger)
def hello_world(query: str) -> str:
    """
    Example function that greets the user.

    Args:
        query (str): The name of the user or a query string.

    Returns:
        str: A greeting message.
    """
    greeting_message = f"{config['greetings']} {query}!!!"
    return greeting_message.capitalize()

Key Points:

  1. Decorator Functionality: The @AthonTool decorator wraps the hello_world function, allowing it to be enhanced with additional features such as configuration management and logging.

  2. Manifest Creation: The decorator automatically generates a manifest for the tool, which can be retrieved using the get_manifest function. This manifest provides essential metadata about the tool, such as its name, description, arguments, and other configuration details.

  3. Tool Endpoint: When the tool is run in a web application, the decorator creates REST API endpoints for the tool. This enables the tool to be invoked over HTTP, making it accessible as a web service.

  4. Direct Manifest Access: The get_manifest function can be used to directly retrieve the tool's manifest, which is useful for documentation, debugging, or integration purposes.

Tool Server Service

The Tool Server Service provides the necessary infrastructure to manage and run LL-Mesh tools on the platform. It includes capabilities for tool discovery and execution, ensuring that tools are easily accessible and can be run efficiently.

  • Tool Discovery: Automatically discover tools within the platform, making them available for use without manual registration.
  • Execution Management: Manage the execution of tools, ensuring that they run efficiently and can be accessed by other services as needed.

Below is a simplified example of how to use the ToolDiscovery class to discover and load tools into an application. This example assumes that the tools can be either local Python modules or remote tools accessible via HTTP.

from athon.agents import ToolRepository
from athon.system import ToolDiscovery

# Example configuration and logger (these should be defined according to your tool's requirements)
projects_config = [
    {
        "name": "Simple Project",
        "tools": [
            "examples/local_tool",           # A local tool path
            "https://127.0.0.1:5003/",       # A remote tool URL
        ]
    }
]
tools_config = {
    "type": "LangChainStructured"
}

def discover_and_load_tools(projects_config, tools_config):
    # Initialize the ToolRepository and ToolDiscovery services
    tool_repository = ToolRepository.create(tools_config)
    tool_discovery = ToolDiscovery()

    # Counter for assigning unique IDs to tools
    tool_id_counter = 1

    # Loop over each project and discover tools
    for project in projects_config:
        for tool_reference in project["tools"]:
            # Discover the tool using the ToolDiscovery service
            tool_info = tool_discovery.discover_tool(tool_reference)

            # If the tool is discovered, add it to the repository
            if tool_info:
                tool_metadata = {
                    "id": tool_id_counter,
                    "project": project["name"],
                    "name": tool_info["name"],
                    "interface": tool_info.get("interface")
                }
                tool_repository.add_tool(tool_info["tool"], tool_metadata)
                tool_id_counter += 1

    return tool_repository

# Run the tool discovery and loading process
tool_repository = discover_and_load_tools(projects_config, tools_config)

# Example output showing the discovered tools
for tool in tool_repository.get_tools().tools:
    print(f"Discovered tool: {tool['name']} from project: {tool['metadata']['project']}")

Key Points:

  1. Tool Discovery: The ToolDiscovery class is used to discover tools, either locally or remotely. For local tools, it loads the tool module and extracts the manifest; for remote tools, it fetches the manifest over HTTP.

  2. Tool Repository: Discovered tools are stored in a ToolRepository, which keeps track of the tools and their metadata, including which project they belong to.

  3. Configuration: The configuration file (config.yaml) defines the projects and their associated tools. Tools can be specified as either local paths or URLs.

  4. Loading Tools: The script iterates through the projects, discovering and loading each tool, then adding them to the repository with unique IDs.