Tool Integration
View SourceTools extend an LLM agent's capabilities by allowing it to perform actions and access information outside of its training data. This guide explains how to create, register, and manage tools in the LLMAgent library.
Tool Architecture
In LLMAgent, tools follow a simple structure:
- Name: A unique identifier for the tool
- Description: Explains what the tool does and how to use it
- Execute Function: A function that implements the tool's behavior
Tools are registered with AgentForge and are available for the LLM to use during a conversation.
Defining Tools
A tool is defined as a map with the following structure:
tool = %{
  name: "weather_api",
  description: "Get the current weather for a location. Requires a 'location' parameter.",
  execute: fn args ->
    location = Map.get(args, "location", "")
    # API call logic here
    %{temperature: 22, conditions: "sunny", location: location}
  end
}The execute function takes a map of arguments and returns the tool's result. The function should be designed to handle errors gracefully and return structured data.
Registering Tools
Tools are registered when creating a conversation flow:
tools = [
  %{name: "calculator", description: "...", execute: &MyApp.Tools.calculate/1},
  %{name: "weather", description: "...", execute: &MyApp.Tools.get_weather/1}
]
{flow, state} = LLMAgent.Flows.conversation("You are a helpful assistant.", tools)Tool Handler Mechanism
When the LLM decides to use a tool, it generates a :tool_call signal. The tool_handler function in LLMAgent.Handlers processes this signal:
- Extracts the tool name and arguments
- Looks up the tool by name
- Executes the tool function
- Creates a :tool_resultsignal with the result
- Adds the tool call and result to the conversation history
Here's a simplified version of how the tool handler works:
def tool_handler(%{type: :tool_call, data: %{name: tool_name, args: tool_args}}, state) do
  case AgentForge.Tools.get(tool_name) do
    {:ok, tool_fn} ->
      try do
        result = tool_fn.(tool_args)
        result_signal = Signals.tool_result(tool_name, result)
        new_state = Store.add_tool_call(state, tool_name, tool_args, result)
        {{:emit, result_signal}, new_state}
      rescue
        e -> 
          error_signal = Signals.error(Exception.message(e), tool_name)
          {{:emit, error_signal}, state}
      end
    {:error, reason} ->
      error_signal = Signals.error("Tool not found: #{reason}", tool_name)
      {{:emit, error_signal}, state}
  end
endTool Result Processing
After a tool is executed, the result is processed by the tool_result_handler:
- The tool result is added to the conversation history
- A new :thinkingsignal is generated
- The LLM is called again to process the tool result
This allows the LLM to use tool results to inform its next actions.
Example Tools
Calculator Tool
calculator_tool = %{
  name: "calculator",
  description: "Perform mathematical calculations. Accepts an 'expression' parameter.",
  execute: fn args ->
    expr = Map.get(args, "expression", "")
    case Code.eval_string(expr) do
      {result, _} -> %{result: result}
      _ -> %{error: "Invalid expression"}
    end
  end
}Web Search Tool
search_tool = %{
  name: "web_search",
  description: "Search the web for information. Requires a 'query' parameter.",
  execute: fn args ->
    query = Map.get(args, "query", "")
    case MyApp.SearchClient.search(query) do
      {:ok, results} -> %{results: results}
      {:error, reason} -> %{error: reason}
    end
  end
}Database Tool
db_tool = %{
  name: "database_query",
  description: "Query a database. Requires a 'query' parameter.",
  execute: fn args ->
    query = Map.get(args, "query", "")
    case Repo.query(query) do
      {:ok, results} -> %{results: results}
      {:error, reason} -> %{error: reason}
    end
  end
}Best Practices
Security Considerations
Be careful with tools that:
- Execute arbitrary code
- Make external API calls
- Access sensitive data
- Modify system state
Always validate and sanitize inputs, especially when the tool has side effects.
Error Handling
Tools should handle errors gracefully and provide useful error messages:
def execute(args) do
  try do
    # Tool logic
    %{result: result}
  rescue
    e in HTTPError -> %{error: "API error: #{e.message}"}
    e in Timeout -> %{error: "Request timed out"}
    _ -> %{error: "Unknown error occurred"}
  end
endStructured Results
Return structured data that's easy for the LLM to understand:
# Good
%{
  weather: %{
    temperature: 22,
    conditions: "sunny",
    humidity: 65
  },
  location: "New York"
}
# Not as good
"Weather in New York: 22°C, sunny, 65% humidity"Tool Description
Write clear, detailed descriptions that explain:
- What the tool does
- What parameters it requires
- What format the parameters should be in
- What the tool will return
Good descriptions help the LLM use tools correctly.
Next Steps
- Learn about custom agents
- Explore LLM provider integration
- See advanced usage patterns