Skip to content
Notifications
Clear all

Quick guide: Adding custom tools for our internal HR system.

3 Posts
3 Users
0 Reactions
14 Views
(@emilyr)
Reputable Member
Joined: 3 months ago
Posts: 295
Topic starter   [#27497]

Integrating CrewAI with proprietary or internal systems is a common requirement that moves beyond the provided example tools. I recently completed a project to connect a CrewAI agent to our internal HRIS (Human Resource Information System) for automated leave balance queries and manager approval workflows. The process, while straightforward in principle, requires careful attention to the tool's interface, error handling, and data formatting to ensure reliability within a multi-agent crew. Below is a detailed breakdown of the implementation, focusing on the custom tool creation.

The core of a custom tool in CrewAI is a class that inherits from `BaseTool`. The critical method is `_execute`, where the core logic resides. For a system like an HRIS, this typically involves constructing an API call. It is paramount to implement robust error handling and sanitize inputs, as the agent will pass raw string arguments.

Here is a concrete example of a tool designed to fetch an employee's remaining Paid Time Off (PTO) balance. This tool assumes the existence of an internal `HRISClient` class that handles authentication and network requests to the actual HR system API.

```python
from crewai_tools import BaseTool
from typing import Type
from pydantic import BaseModel, Field

class HRISGetPTOBalanceToolInput(BaseModel):
"""Input schema for HRISGetPTOBalanceTool."""
employee_id: str = Field(..., description="The unique company identifier for the employee.")

class HRISGetPTOBalanceTool(BaseTool):
name: str = "Get Employee PTO Balance"
description: str = "Fetches the remaining Paid Time Off (PTO) hours for a specified employee from the internal HR system."
args_schema: Type[BaseModel] = HRISGetPTOBalanceToolInput
_verbose: bool = True

def _run(self, employee_id: str) -> str:
"""
Executes the tool to retrieve PTO balance.

Args:
employee_id (str): The company employee ID.

Returns:
str: A formatted string containing the balance or an error message.
"""
try:
# Initialize your internal HRIS client. Credentials should be from environment variables.
hris_client = HRISClient(
api_key=os.getenv("HRIS_API_KEY"),
base_url=os.getenv("HRIS_BASE_URL")
)

# Make the API call to your internal service
# The `get_pto_balance` method is hypothetical and maps to your actual HRIS API endpoint.
response = hris_client.get_pto_balance(employee_id)

# Parse and format the response for the agent
# Assume response is a dict: {'remaining_hours': 45.5, 'fiscal_year': '2024'}
remaining_hours = response.get('remaining_hours', 0)
fiscal_year = response.get('fiscal_year', 'current')

return f"Employee {employee_id} has {remaining_hours} hours of PTO remaining for the {fiscal_year} fiscal year."

except HRISClient.AuthenticationError:
return "Error: Authentication failed with the HR system."
except HRISClient.NotFoundError:
return f"Error: No employee found with ID {employee_id}."
except Exception as e:
# Log the full exception for debugging, but return a generic message to the agent
logger.error(f"HRIS API call failed: {e}")
return "Error: Unable to retrieve PTO information at this time."
```

Key considerations for production use:

* **Input Validation:** The `args_schema` (Pydantic model) provides strong validation before `_run` is called. This prevents malformed requests from reaching your internal API.
* **Error Handling:** The tool must never raise an uncaught exception. All possible failures (network, auth, data parsing) must be caught and returned as a clear string message. This allows the agent to handle the failure gracefully within its task flow.
* **State & Sessions:** Avoid storing session state within the tool instance. Each call should be stateless and independent, managing necessary authentication via the client object initialized inside `_run`.
* **Tool Description:** The `description` field is crucial. It is used by the LLM to decide when to call this tool. Be explicit about the function and the precise format of the required `employee_id`.
* **Cost & Latency:** Be mindful that calls to internal APIs add latency. In a complex crew workflow, serial calls to a slow HRIS API can significantly impact total execution time. Consider timeouts and potentially caching strategies for read-heavy tools.

Once defined, the tool can be instantiated and assigned to an agent like any other:
```python
pto_tool = HRISGetPTOBalanceTool()
hr_agent = Agent(
role='HR Specialist',
goal='Accurately provide employee HR data',
tools=[pto_tool, ...],
...
)
```

For more complex interactions, such as submitting an approval request which modifies system state, you would follow the same pattern but with a POST/PUT request in the `_run` method and an equally rigorous `args_schema` defining all required parameters (e.g., `request_id`, `manager_id`, `approval_decision`). The return value should confirm the action taken and include a transaction identifier for traceability.



   
Quote
(@doray)
Estimable Member
Joined: 2 months ago
Posts: 145
 

That's the happy path. What's your plan when the HRISClient's auth token expires mid-run, or the internal API returns a 503?

Everyone forgets to budget for the circuit breaker and retry logic. Then you're debugging a silent crew failure at 2am.


Show me the logs.


   
ReplyQuote
(@darrenk)
Honorable Member
Joined: 3 months ago
Posts: 392
 

You're totally right. That 2am debugging scenario is a nightmare I've lived through, too. For auth tokens, we put a tiny wrapper around our HRISClient that checks expiry and refreshes before each call, just a few extra lines. It's saved us a bunch of headaches.

But the 503s are trickier. We started with simple retries but ended up adding a small decorator to log failures and pause the whole crew's task queue for a minute before trying again. It's not perfect, but it keeps things from going completely off the rails.


dk


   
ReplyQuote