LemonAgent. Types. AgentTool
(lemon_agent v0.1.0)
View Source
A tool the agent loop can call: a schema the model sees plus the function that runs when it calls it.
This is the unit every tool in the platform reduces to, whether it is a
built-in, an MCP tool adapted by LemonMcp.ToolAdapter, or one contributed
at runtime by an app the platform does not know about (see
LemonAgent.ToolRegistry).
The shape
:name— what the model calls. Stable, lowercase,snake_case; it is matched against the model's tool call after whitespace normalisation, and it is the key everything else in the platform uses. Renaming one invalidates prompts and cached tool schemas, so treat it as an identity.:description— what the model reads to decide whether to call it. This is prompt text, and it is the main lever on whether the tool gets used correctly; say what the tool does, when to use it and what its limits are.:parameters— a JSON Schema object ("type" => "object"with"properties"and"required"), passed to the provider as-is. String keys, not atoms.:label— a short human-readable name for UI display. Never shown to the model.:execute— the function below.
The execute function
It is called as execute.(tool_call_id, params, signal, on_update):
tool_call_id— this invocation's id, for correlating events.params— the arguments the model produced, decoded from JSON, so string keys and model-supplied values. Validate them; a model will eventually send a missing field or the wrong type, and a clear{:error, message}teaches it to retry correctly.signal— an abort signal (seeLemonAgent.AbortSignal), ornil. Long-running tools should check it and return early; ignoring it means a cancelled run keeps working.on_update— a one-argument callback taking a partialAgentToolResult, ornil. Calling it pushes a streaming update to the UI. It is fire-and-forget and always returns:ok.
Return an AgentToolResult, {:ok, result} (equivalent), or
{:error, reason}, which the loop turns into an error result the model can
read and react to. Anything else is treated as an error.
The loop wraps execution in try, so a raise or throw becomes an error
result rather than killing the run — but the model then gets an exception
message instead of an explanation. Handle your own failures: a tool that is
not configured should say so in its result (as
XApi.Tools.PostToX does when its credentials are missing), not raise.
The module convention
A tool lives in a module exposing tool/1 (options) and tool/2 (cwd plus
options) that build the struct. Both are expected — consumers that have a
working directory call tool/2, those that do not call tool/1 — and the
usual definition is def tool(_cwd, opts), do: tool(opts). Building the
struct on each call rather than at compile time is what lets the
description and parameters reflect current configuration.
defmodule MyApp.Tools.Greet do
alias LemonAgent.Types.{AgentTool, AgentToolResult}
alias LemonAi.Types.TextContent
@spec tool(keyword()) :: AgentTool.t()
def tool(_opts \\ []) do
%AgentTool{
name: "greet",
label: "Greet",
description: "Greet someone by name.",
parameters: %{
"type" => "object",
"properties" => %{
"name" => %{"type" => "string", "description" => "Who to greet"}
},
"required" => ["name"]
},
execute: &execute(&1, &2, &3, &4)
}
end
def tool(_cwd, opts), do: tool(opts)
def execute(_id, %{"name" => name}, _signal, _on_update) when is_binary(name) do
%AgentToolResult{content: [%TextContent{text: "Hello, " <> name <> "!"}]}
end
def execute(_id, _params, _signal, _on_update) do
{:error, "Missing required parameter: name"}
end
end
Summary
Types
@type execute_fn() :: (tool_call_id :: String.t(), params :: map(), signal :: reference() | nil, on_update :: on_update() | nil -> LemonAgent.Types.AgentToolResult.t() | {:ok, LemonAgent.Types.AgentToolResult.t()} | {:error, term()})
@type on_update() :: (LemonAgent.Types.AgentToolResult.t() -> :ok)
@type t() :: %LemonAgent.Types.AgentTool{ description: String.t(), execute: execute_fn(), label: String.t(), name: String.t(), parameters: map() }