Skip to main content

Build a Custom Tool

Agents become useful when they can do things -- call APIs, query databases, read files, transform data. In XAIBO, tools are Python functions decorated with @tool and placed in the tools/ directory of your agent's workspace. The PFC module auto-discovers them at startup and exposes them to the LLM.

This tutorial walks you through writing a tool, using the built-in platform client for API calls, and deploying it.

Prerequisites

  • A running custom agent (Create a Custom Agent)
  • Basic Python knowledge
  • Familiarity with your agent's directory structure

Steps

1. Create a tool file

Tools live in the tools/ directory inside your agent's workspace. Each .py file in this directory is scanned at startup for functions decorated with @tool.

Create a new file at tools/weather_tools.py:

# tools/weather_tools.py
from xaibo.primitives.protocol import tool


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

Args:
city: The city name (e.g., "Tokyo", "New York")
units: Temperature units -- "celsius" or "fahrenheit"

Returns:
Current weather description with temperature
"""
import requests

resp = requests.get(
f"https://api.weather.example/v1/{city}", # placeholder URL -- replace with a real weather API
params={"units": units}
)
data = resp.json()
return f"{city}: {data['temp']}° {units}, {data['condition']}"

A few things to note about the @tool decorator:

  • The function name becomes the tool name the LLM sees.
  • The docstring becomes the tool description. Write it like you are explaining the tool to a colleague -- the LLM uses this to decide when and how to call it.
  • Type hints on parameters are required. They tell the LLM what arguments the tool expects.
  • Default values make parameters optional.
info

The @tool decorator comes from xaibo.primitives.protocol. This is the only import you need from the framework -- everything else is standard Python.

2. Use the platform client for API calls

If your tool needs to call the XpressAI Platform API (to manage tasks, read conversations, etc.), use the built-in PlatformClient:

# tools/task_tools.py
from xaibo.primitives.protocol import tool
from tools.platform_client import PlatformClient

client = PlatformClient() # auto-authenticates via XPRESSAI_API_TOKEN


@tool
def list_my_tasks() -> str:
"""List all tasks currently assigned to this agent."""
response = client.get("/api/tasks?status=Doing")
return response.text


@tool
def complete_task(task_id: int) -> str:
"""Mark a task as done.

Args:
task_id: The ID of the task to complete
"""
response = client.put(
f"/api/tasks/{task_id}/status",
json={"status": "Done"}
)
return f"Task {task_id} marked as Done"

The PlatformClient handles authentication automatically. Under the hood, it sends these headers with every request:

HeaderValuePurpose
X-Task-AuthorizationSHA-1(projectId + tokenSecret)Authenticates the agent to the platform API
X-Task-NamespaceK8s namespaceIdentifies the cluster namespace the agent runs in
X-Agent-NameAgent service nameIdentifies which agent is making the request

You do not need to set these headers manually -- PlatformClient reads them from the agent's environment variables.

3. Handle file operations safely

Tools run inside the agent container with access to /data/home/. If your tool reads or writes files, use the _safe_path() utility from platform_client (included in the agent template) to prevent directory traversal attacks. _safe_path() resolves and validates the given filename against the agent's data directory, returning an absolute path that is guaranteed to stay within the allowed directory:

# tools/file_tools.py
from xaibo.primitives.protocol import tool
from tools.platform_client import _safe_path


@tool
def read_report(filename: str) -> str:
"""Read a report file from the agent's data directory.

Args:
filename: Name of the file to read (e.g., "q2-report.txt")
"""
safe = _safe_path(filename)
with open(safe, "r") as f:
return f.read()
warning

Tools run inside the agent container with access to /data/home/. Always use the _safe_path() utility from platform_client (included in every agent template) to validate file paths. Without it, a crafted filename like ../../etc/passwd could escape the intended directory.

4. Restart the agent

After adding or modifying tool files, restart the agent to pick up the changes:

  1. Go to the Agents page in the platform UI.
  2. Find your agent and click Restart.
  3. Wait for the status to return to Running.

On startup, XAIBO scans the tools/ directory, discovers all @tool-decorated functions, and registers them with the PFC module. The PFC then includes these tools in its system prompt so the LLM knows they are available.

5. Test your tool

Open a conversation with your agent and ask it to do something that requires the new tool. For the weather example:

What's the weather like in Tokyo?

The agent's PFC will see the get_weather tool in its available tools list, match it to your request, and call it with city="Tokyo". You'll see the tool call and its result in the conversation.

If the tool does not appear, check:

  • The file is in the tools/ directory (not a subdirectory).
  • The function has the @tool decorator.
  • The function has type hints on all parameters.
  • You restarted the agent after adding the file.

6. Write tools that compose well

Good tools are small, focused, and composable. Rather than one large tool that does everything, write several tools that each do one thing:

@tool
def search_customers(query: str) -> str:
"""Search for customers by name or email."""
...

@tool
def get_customer_orders(customer_id: int) -> str:
"""Get recent orders for a specific customer."""
...

@tool
def summarize_order_trends(orders_json: str) -> str:
"""Analyze a list of orders and identify trends."""
...

The PFC orchestrator is good at chaining tools together. If you ask "What are the buying trends for Acme Corp?", it will call search_customers, then get_customer_orders, then summarize_order_trends -- without you writing any orchestration code.

tip

Return strings from your tools, not complex objects. The LLM processes tool results as text. If you need to return structured data, return JSON-formatted strings -- the LLM handles them well.

What you've done

  • Created a Python tool file with the @tool decorator
  • Used the PlatformClient for authenticated API calls to the platform
  • Learned about the authentication headers agents send
  • Applied _safe_path() for secure file operations
  • Restarted the agent to pick up the new tool
  • Tested the tool through a conversation

Next steps

Now that your agent has custom tools, head to Automate Tasks via API to learn how to create and manage tasks programmatically through the REST API.


See also