Skip to main content

Outline

In this module, we'll focus on the practical aspects of defining your custom tools using the optimizely-opal.opal-tools-sdk. We'll cover the syntax for applying the @tool decorator, how to structure your tool functions to accept parameters using Python's type hints, and how to define parameter schemas.

By the end of this module, you will be able to:

  • Define and register custom Opal tools using Python decorators.

  • Structure tool functions to accept and validate various parameter types using Python type hints and Pydantic models.

  • Ensure automatic parameter validation and manifest generation.

  • Implement authentication and security mechanisms for Python tools.

Using the @tool Decorator to Mark a Function as an Opal Tool

As introduced in the previous, the @tool decorator is how you tell the SDK that a particular async function should be exposed as an Opal tool.

Basic Structure:

from optimizely_opal.opal_tools_sdk import tool
from pydantic import BaseModel # Useful for complex parameters

# Define a Pydantic model for parameters if needed
class MyToolParams(BaseModel):
    input_string: str
    input_number: int = 0 # Parameter with a default value

@tool(name="my_python_tool", description="A simple Python tool example.")
async def my_python_tool_function(params: MyToolParams):
    """
    This function processes the input string and number.
    """
    print(f"Tool executed with string: {params.input_string}, number: {params.input_number}")
    result = f"Processed: {params.input_string} and {params.input_number * 2}"
    return {"status": "success", "output": result}
  • name (str): A crucial identifier. It should be unique within your tool service and descriptive, often using snake_case. This is what Opal's AI will use to refer to your tool.
  • description (str): A human-readable explanation of the tool's purpose. It's used in the Opal UI. Make it clear and concise.
  • Function Signature: The async function's parameters (e.g., params: MyToolParams) are automatically used by the SDK to generate the tool's schema.
  • Return Value: The async function should return a dictionary or a Pydantic model instance, which will be serialized to JSON as the response Opal receives.

Structuring Your Tool Functions to Accept Parameters

When Opal invokes your tool, it sends the input data as a JSON object in the request body. The SDK, in conjunction with FastAPI and Pydantic, automatically parses this and passes it to your async function.

Using Python Type Hints for Simple Parameters: For tools with a few simple parameters, you can define them directly in the function signature.

@tool(name="add_numbers", description="Adds two numbers.")
async def add_numbers_tool(num1: float, num2: float):
    """
    Adds two floating-point numbers.
    """
    result = num1 + num2
    return {"sum": result}

Note: The SDK will automatically infer the parameters for the manifest from these type hints.

Using Pydantic Models for Complex Parameters: For tools with many parameters, nested structures, or specific validation rules, Pydantic models are highly recommended. They provide strong type checking, data validation, and clear structure.

from pydantic import BaseModel, Field
from typing import Optional, List

class UserProfileUpdate(BaseModel):
    user_id: str = Field(..., description="Unique identifier for the user.")
    email: Optional[str] = Field(None, description="New email address for the user.")
    is_active: Optional[bool] = Field(None, description="Whether the user account is active.")
    roles: List[str] = Field([], description="List of roles assigned to the user.")

@tool(name="update_user_profile", description="Updates a user's profile information.")
async def update_user_profile_tool(profile_data: UserProfileUpdate):
    """
    Updates a user profile in a hypothetical system.
    """
    print(f"Updating user {profile_data.user_id} with data: {profile_data.dict()}")
    # Simulate database update or API call
    updated_fields = profile_data.dict(exclude_unset=True) # Only show fields that were provided
    return {"status": "success", "user_id": profile_data.user_id, "updated_fields": updated_fields}
  • Field from Pydantic allows adding descriptions and other validation rules that will be reflected in the Opal tool manifest.
  • Optional from typing makes a parameter optional.

Defining Parameter Schemas

While the SDK often infers the schema from your function signatures and Pydantic models, understanding how it translates to the Opal Tool Manifest is important for debugging and advanced scenarios. The SDK automatically generates the parameters array in the manifest based on your Python function's arguments and their type hints/Pydantic definitions.

Key Takeaways:

  • The @tool decorator is your primary interface for defining tools in Python.
  • async functions are used for tool implementation.
  • Python type hints are crucial for defining tool parameters and enabling automatic schema generation and validation.
  • Pydantic models are highly recommended for complex parameter structures, providing robust validation and clear documentation.
  • Your tool function should return a dictionary or Pydantic model instance that can be serialized to JSON.

In the next module, we'll build on these concepts to create more practical Opal tools that integrate with backend systems.