Skip to main content

Basic Tool

Create a tool with the @tool decorator:
from reminix_runtime import tool, serve

@tool
async def get_weather(location: str, units: str = "celsius") -> dict:
    """Get the current weather for a city."""
    return {
        "location": location,
        "temperature": 22,
        "units": units,
        "condition": "sunny"
    }

serve(tools=[get_weather], port=8080)
The decorator automatically extracts:
  • Name: From the function name (get_weather)
  • Description: From the docstring (first paragraph)
  • Parameters: From type hints and default values
  • Parameter descriptions: From docstring Args: section (Google, NumPy, or Sphinx style)
  • Output schema: From the return type hint (supports Pydantic, TypedDict, and basic types)

Custom Name and Description

Override the defaults:
@tool(name="weather", description="Fetch current weather data for any city")
async def get_weather(location: str) -> dict:
    """This docstring is ignored when description is provided."""
    return {"location": location, "temp": 22}

Sync vs Async

Both sync and async functions work:
# Async tool
@tool
async def fetch_data(url: str) -> dict:
    """Fetch data from a URL."""
    # Use async HTTP client
    return {"data": "..."}

# Sync tool
@tool
def calculate(a: float, b: float, operation: str) -> dict:
    """Perform basic math operations."""
    if operation == "add":
        return {"result": a + b}
    elif operation == "multiply":
        return {"result": a * b}
    return {"error": f"Unknown operation: {operation}"}

Type Hints and Schema

Type hints are converted to JSON Schema:
@tool
def process(
    text: str,           # type: "string", required
    count: int,          # type: "integer", required
    score: float,        # type: "number", required
    enabled: bool,       # type: "boolean", required
    tags: list,          # type: "array", required
    metadata: dict,      # type: "object", required
    limit: int = 10,     # type: "integer", optional (has default)
) -> dict:
    """Process data with various types."""
    return {"processed": True}
Generated schema:
{
  "properties": {
    "text": { "type": "string" },
    "count": { "type": "integer" },
    "score": { "type": "number" },
    "enabled": { "type": "boolean" },
    "tags": { "type": "array" },
    "metadata": { "type": "object" },
    "limit": { "type": "integer", "default": 10 }
  },
  "required": ["text", "count", "score", "enabled", "tags", "metadata"]
}

Parameter Descriptions from Docstrings

Parameter descriptions are automatically extracted from docstrings (Google, NumPy, or Sphinx style):
@tool
def search_products(
    query: str,
    category: str = "all",
    limit: int = 10
) -> dict:
    """Search for products in the catalog.

    Args:
        query: Search keywords or product name
        category: Product category to filter by
        limit: Maximum number of results to return
    """
    return {"results": [...]}
Generated input schema:
{
  "properties": {
    "query": {
      "type": "string",
      "description": "Search keywords or product name"
    },
    "category": {
      "type": "string",
      "description": "Product category to filter by",
      "default": "all"
    },
    "limit": {
      "type": "integer",
      "description": "Maximum number of results to return",
      "default": 10
    }
  },
  "required": ["query"]
}
Supported docstring formats: Google style (Args:), NumPy style (Parameters), and Sphinx style (:param name:).

Generic Types

Use parameterized generics for more specific schemas:
@tool
def search(
    query: str,
    filters: dict[str, str],     # type: "object"
    results: list[str],          # type: "array" with items: "string"
) -> list[dict]:
    """Search with typed parameters."""
    return [{"match": query}]

Error Handling

Return errors as part of the output or raise exceptions:
@tool
def divide(a: float, b: float) -> dict:
    """Divide two numbers."""
    if b == 0:
        # Return error in output
        return {"error": "Cannot divide by zero"}
    return {"result": a / b}

@tool
def validate(data: dict) -> dict:
    """Validate data with exception."""
    if "required_field" not in data:
        # Raise exception (caught and returned as error)
        raise ValueError("Missing required_field")
    return {"valid": True}
When an exception is raised, the response includes an error field:
{
  "output": null,
  "error": "Missing required_field"
}

Output Schema

Return type hints become the output schema in metadata. For rich schemas with property details, use Pydantic models or TypedDict. Pydantic models provide full schema generation with descriptions and validation:
from pydantic import BaseModel, Field
from reminix_runtime import tool

class UserOutput(BaseModel):
    """User information."""
    id: str = Field(description="Unique user identifier")
    name: str = Field(description="Display name")
    email: str = Field(description="Email address")

@tool
def get_user(user_id: str) -> UserOutput:
    """Get user information."""
    return UserOutput(id=user_id, name="John", email="[email protected]")
Generated output schema:
{
  "output": {
    "type": "object",
    "properties": {
      "id": { "type": "string", "description": "Unique user identifier" },
      "name": { "type": "string", "description": "Display name" },
      "email": { "type": "string", "description": "Email address" }
    },
    "required": ["id", "name", "email"]
  }
}

TypedDict (Simpler Alternative)

For simpler cases without validation, use TypedDict:
from typing import TypedDict
from reminix_runtime import tool

class WeatherOutput(TypedDict):
    location: str
    temperature: int
    condition: str

@tool
def get_weather(location: str) -> WeatherOutput:
    """Get weather for a location."""
    return {"location": location, "temperature": 72, "condition": "sunny"}
Generated output schema:
{
  "output": {
    "type": "object",
    "properties": {
      "location": { "type": "string" },
      "temperature": { "type": "integer" },
      "condition": { "type": "string" }
    },
    "required": ["location", "temperature", "condition"]
  }
}

Basic Types

For simple returns, basic types work fine:
@tool
def echo(text: str) -> str:
    """Echo the input."""
    return text

@tool
def count_words(text: str) -> int:
    """Count words in text."""
    return len(text.split())
Using -> dict without Pydantic or TypedDict results in {"type": "object"} with no property details. Use Pydantic or TypedDict for rich schemas.

Serving Tools

Serve one or more tools:
from reminix_runtime import tool, serve

@tool
def tool_a(x: str) -> dict:
    """Tool A."""
    return {"a": x}

@tool
def tool_b(y: int) -> dict:
    """Tool B."""
    return {"b": y}

serve(tools=[tool_a, tool_b], port=8080)

API Endpoints

When tools are served, these endpoints are available:
EndpointMethodDescription
/healthGETHealth check
/infoGETDiscovery (lists all tools with schemas)
/tools/{name}/callPOSTCall a tool

Call a Tool

curl -X POST http://localhost:8080/tools/get_weather/call \
  -H "Content-Type: application/json" \
  -d '{"input": {"location": "San Francisco", "units": "fahrenheit"}}'
Response:
{
  "output": {
    "location": "San Francisco",
    "temperature": 65,
    "units": "fahrenheit",
    "condition": "foggy"
  }
}

Discovery

curl http://localhost:8080/info
Response:
{
  "tools": [
    {
      "name": "get_weather",
      "type": "tool",
      "description": "Get the current weather for a city.",
      "input": {
        "type": "object",
        "properties": {
          "location": { "type": "string" },
          "units": { "type": "string", "default": "fahrenheit" }
        },
        "required": ["location"]
      },
      "output": { "type": "object" }
    }
  ]
}

With Context

Access request context in tool execution:
@tool
async def secure_action(resource_id: str) -> dict:
    """Perform a secure action."""
    # Context is passed via the execute request
    # Access it through the request object in custom implementations
    return {"resource": resource_id, "status": "completed"}

Complete Example

"""
Runtime Tools Example

Usage:
    uv run python main.py

Test endpoints:
    curl http://localhost:8080/health
    curl http://localhost:8080/info
    curl -X POST http://localhost:8080/tools/get_weather/call \
      -H "Content-Type: application/json" \
      -d '{"input": {"location": "San Francisco"}}'
"""

from pydantic import BaseModel, Field
from reminix_runtime import serve, tool


# Define output schemas with Pydantic
class WeatherOutput(BaseModel):
    """Weather information for a location."""
    location: str = Field(description="City name")
    temperature: int = Field(description="Current temperature")
    units: str = Field(description="Temperature units")
    condition: str = Field(description="Weather condition")


class CalculationOutput(BaseModel):
    """Result of a math calculation."""
    a: float = Field(description="First operand")
    b: float = Field(description="Second operand")
    operation: str = Field(description="Operation performed")
    result: float = Field(description="Calculation result")


WEATHER_DATA = {
    "san francisco": {"temp": 65, "condition": "foggy"},
    "new york": {"temp": 45, "condition": "cloudy"},
    "los angeles": {"temp": 75, "condition": "sunny"},
}


@tool
async def get_weather(location: str, units: str = "fahrenheit") -> WeatherOutput:
    """Get the current weather for a city.

    Args:
        location: City name (e.g., "San Francisco", "New York")
        units: Temperature units - "celsius" or "fahrenheit"
    """
    weather = WEATHER_DATA.get(location.lower())
    if not weather:
        raise ValueError(f'Weather data not available for "{location}"')

    temp = weather["temp"]
    if units == "celsius":
        temp = round((temp - 32) * 5 / 9)

    return WeatherOutput(
        location=location,
        temperature=temp,
        units=units,
        condition=weather["condition"],
    )


@tool
def calculate(a: float, b: float, operation: str) -> CalculationOutput:
    """Perform basic math operations.

    Args:
        a: First operand
        b: Second operand
        operation: Math operation - "add", "subtract", "multiply", or "divide"
    """
    if operation == "add":
        result = a + b
    elif operation == "subtract":
        result = a - b
    elif operation == "multiply":
        result = a * b
    elif operation == "divide":
        if b == 0:
            raise ValueError("Cannot divide by zero")
        result = a / b
    else:
        raise ValueError(f"Unknown operation: {operation}")

    return CalculationOutput(a=a, b=b, operation=operation, result=result)


if __name__ == "__main__":
    serve(tools=[get_weather, calculate], port=8080)

Next Steps