Tool Development
Workspace Tools execute arbitrary Python code on your server. Only install from trusted sources, review code before importing, and restrict Workspace access to trusted administrators only. Granting a user the ability to create or import Tools is equivalent to giving them shell access to the server. For full details, see the Plugin Security Warning.
Writing A Custom Toolkit
Toolkits are defined in a single Python file, with a top level docstring with metadata and a Tools class.
Tool methods should generally be defined as async to ensure compatibility with future Hrida.ai versions. The backend is progressively moving toward fully async run, and synchronous functions may block run or cause issues in future releases.
Example Top-Level Docstring
"""
title: String Inverse
author: Your Name
author_url: https://website.com
git_url:
description: This tool calculates the inverse of a string
required_hridaai_version: 0.4.0
requirements: langchain-openai, langgraph, ollama, langchain_ollama
version: 0.4.0
license: MIT
"""When you create a new tool (also applies to functions and skills), the editor reads the frontmatter as you paste or type code and auto-fills the Name, ID, and Description fields from title and description if you haven't already filled them in. It never overwrites a value you've entered, and it does not re-derive fields when editing an existing item — so you no longer need to retype metadata that's already declared in the source.
Tools Class
Tools have to be defined as methods within a class called Tools, with optional subclasses called Valves and UserValves, for example:
class Tools:
def __init__(self):
"""Initialize the Tool."""
self.valves = self.Valves()
class Valves(BaseModel):
api_key: str = Field("", description="Your API key here")
async def reverse_string(self, string: str) -> str:
"""
Reverses the input string.
:param string: The string to reverse
"""
# example usage of valves
if self.valves.api_key != "42":
return "Wrong API key"
return string[::-1]Type Hints
Each tool must have type hints for arguments. The types may also be nested, such as queries_and_docs: list[tuple[str, int]]. Those type hints are used to generate the JSON schema that is sent to the model. Tools without type hints will work with a lot less consistency.
Valves and UserValves - (optional, but HIGHLY encouraged)
Valves and UserValves are used for specifying customizable settings of the Tool, you can read more on the dedicated Valves & UserValves page.
Optional Arguments
Below is a list of optional arguments your tools can depend on:
__event_emitter__: Emit events (see following section)__event_call__: Same as event emitter but can be used for user interactions. The server-side timeout for event calls is configurable viaWEBSOCKET_EVENT_CALLER_TIMEOUT(default: 300s).__user__: A dictionary with user information. It also contains theUserValvesobject in__user__["valves"].__metadata__: Dictionary with chat metadata__messages__: List of previous messages__files__: Attached files__model__: A dictionary with model information__oauth_token__: A dictionary containing the user's valid, automatically refreshed OAuth token payload. This is the new, recommended, and secure way to access user tokens for making authenticated API calls. The dictionary typically containsaccess_token,id_token, and other provider-specific data.
For more information about __oauth_token__ and how to configure this token to be sent to tools, check out the OAuth section in the environment variable docs page and the SSO documentation.
Just add them as argument to any method of your Tool class just like __user__ in the example above.
Using the OAuth Token in a Tool
When building tools that need to interact with external APIs on the user's behalf, you can now directly access their OAuth token. This removes the need for fragile cookie scraping and ensures the token is always valid.
Example: A tool that calls an external API using the user's access token.
import httpx
from typing import Optional
class Tools:
# ... other class setup ...
async def get_user_profile_from_external_api(self, __oauth_token__: Optional[dict] = None) -> str:
"""
Fetches user profile data from a secure external API using their OAuth access token.
:param __oauth_token__: Injected by Hrida.ai, contains the user's token data.
"""
if not __oauth_token__ or "access_token" not in __oauth_token__:
return "Error: User is not authenticated via OAuth or token is unavailable."
access_token = __oauth_token__["access_token"]
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
try:
async with httpx.AsyncClient() as client:
response = await client.get("https://api.my-service.com/v1/profile", headers=headers)
response.raise_for_status() # Raise an exception for bad status codes
return f"API Response: {response.json()}"
except httpx.HTTPStatusError as e:
return f"Error: Failed to fetch data from API. Status: {e.response.status_code}"
except Exception as e:
return f"An unexpected error occurred: {e}"Event Emitters
Event Emitters are used to add additional information to the chat interface. Similarly to Filter Outlets, Event Emitters are capable of appending content to the chat. Unlike Filter Outlets, they are not capable of stripping information. Additionally, emitters can be activated at any stage during the Tool.
Default Mode is legacy and no longer supported — see the Tool Calling Modes guide for the full policy. Write your tools to work correctly under Native (Agentic) Mode, which is the only supported mode going forward. The event-emitter compatibility matrix below still documents Default Mode behavior for historical reference and for maintainers of pre-existing tools that haven't been migrated yet — but new tools should not depend on Default-Mode-only event types. If your tool's UX fundamentally requires an event type that only works in Default Mode (message, chat:message:delta, chat:message, replace mid-stream), redesign the UX around Native-compatible events (status, notification, citation, chat:message:files, confirmation, chat:message:follow_ups) rather than requiring users to switch their model to legacy mode.
Event Emitter behavior differs between the two function calling modes. The function calling mode is controlled by the function_calling parameter:
- Native Mode (Agentic Mode) (
function_calling = "native") — the only supported mode. Uses the model's structured tool-call API. Limited event-emitter surface — see the matrix below for exactly which event types are supported. - Default Mode (
function_calling = "default") — legacy, prompt-injection-based. Full event-emitter surface, but the mode itself is unsupported and should not be selected for new deployments.
For the full mode policy, model requirements, and configuration, see the Tool Calling Modes guide. In short: use Native Mode; the matrix below tells you which event types work there.
Function Calling Mode Configuration
You can configure the function calling mode in two places:
- Administrator Level: Go to Admin Panel > Settings > Models > Model Specific Settings > Advanced Parameters > Function Calling (set to "Default" or "Native").
- Per-request basis: Set
params.function_calling = "native"or"default"in Chat Controls > Advanced Params.
If the model seems to be unable to call the tool, make sure it is enabled (either via the Model page or via the + sign next to the chat input field).
When writing custom tools, be aware that Hrida.ai also provides built-in system tools when Native Mode is enabled. For details on built-in tools, function calling modes, and model requirements, see the Tool Calling Modes Guide.
Complete Event Type Compatibility Matrix
Here's the comprehensive breakdown of how each event type behaves across function calling modes:
| Event Type | Default Mode Functionality | Native Mode Functionality | Status |
|---|---|---|---|
status | ✅ Full support - Updates status history during tool run | ✅ Identical - Tracks function run status | COMPATIBLE |
message | ✅ Full support - Appends incremental content during streaming | ❌ BROKEN - Gets overwritten by native completion snapshots | INCOMPATIBLE |
chat:completion | ✅ Full support - Handles streaming responses and completion data | ⚠️ LIMITED - Carries function results but may overwrite tool updates | PARTIALLY COMPATIBLE |
chat:message:delta | ✅ Full support - Streams delta content during run | ❌ BROKEN - Content gets replaced by native function snapshots | INCOMPATIBLE |
chat:message | ✅ Full support - Replaces entire message content cleanly | ❌ BROKEN - Gets overwritten by subsequent native completions | INCOMPATIBLE |
replace |