ALLM.Tool (allm v0.4.1)

Copy Markdown View Source

A tool the model may call — Layer A serializable data plus an optional runtime :handler.

The struct itself is pure data (:name, :description, :schema, :metadata, :manual are all serializable), but :handler may be an anonymous function. A tool with a fn handler is not safe to persist via :erlang.term_to_binary/1; persist either :handler | nil and re-attach at load time, or use a {Module, :function} tuple.

Per-tool manual mode

Setting manual: true declares this tool will not be auto-executed by ALLM.chat/3 even when the call uses mode: :auto. When the assistant emits a tool call for a manual: true tool, the chat orchestrator halts the loop with halted_reason: :manual_tool_calls and surfaces the call in metadata.manual_tool_calls. The caller resolves the tool result either via ALLM.Session.submit_tool_result/3 (when running through the Session API) or by appending a :tool message to the thread and re-issuing ALLM.chat/3 (raw stateless flow).

Default: false — the tool is auto-executed under mode: :auto. The flag is silent under whole-loop mode: :manual; the whole-loop short-circuit fires before the per-tool partition runs.

See also guides/tools.md.

Summary

Types

Tool handler — called with parsed arguments (arity 1) or with arguments plus a caller-supplied context keyword list (arity 2).

Legal returns from a tool handler.

t()

Functions

Build a %Tool{} from keyword opts.

Types

handler()

@type handler() ::
  (map() -> handler_result()) | (map(), keyword() -> handler_result())

Tool handler — called with parsed arguments (arity 1) or with arguments plus a caller-supplied context keyword list (arity 2).

The arity-2 keyword list carries call context. Standard keys provided by ALLM.ToolExecutor.Default:

KeyTypeNotes
:contextterm()The opaque value passed via ALLM.chat(engine, thread, context: ...) or Session.reply(session, msg, context: ...). Caller-defined shape.
:session_idString.t() | nilThe %Session{}.id when invoked through the Session API; nil for stateless chat/3 / step/3.
:tool_call%ALLM.ToolCall{}The exact tool call the assistant emitted (:id, :name, :arguments).
:engine%ALLM.Engine{}The engine driving the call — handlers needing to issue downstream LLM calls reuse it via ALLM.generate/3 etc.
:request_idString.t() | nilTelemetry-correlation id from the parent span.

Custom keys in :context are passed through unchanged. The arity-1 form is preferred when handlers don't need context; arity-2 is detected at dispatch time via :erlang.fun_info(handler, :arity).

handler_result()

@type handler_result() ::
  {:ok, term()}
  | {:error, term()}
  | {:ask_user, String.t()}
  | {:ask_user, String.t(), keyword()}
  | {:halt, atom(), term()}

Legal returns from a tool handler.

schema()

@type schema() :: map()

t()

@type t() :: %ALLM.Tool{
  description: String.t(),
  handler: handler() | nil,
  manual: boolean(),
  metadata: map(),
  name: String.t(),
  schema: schema()
}

Functions

new(opts)

@spec new(keyword()) :: t()

Build a %Tool{} from keyword opts.

:name, :description, and :schema are required; omitting any raises ArgumentError via struct!/2. :handler is optional — a tool may be declared without a handler when the caller handles tool calls manually.

:manual is optional and defaults to false. It MUST be a boolean passing :manual with a non-boolean value (including nil) raises ArgumentError. The defstruct default protects against omission, but struct!/2 accepts an explicit nil (silently overwriting the default), so this constructor adds an explicit guard.

Examples

iex> tool = ALLM.Tool.new(name: "weather", description: "weather by city", schema: %{"type" => "object"})
iex> tool.name
"weather"
iex> tool.handler
nil
iex> tool.manual
false

iex> tool = ALLM.Tool.new(name: "charge", description: "charge a card", schema: %{"type" => "object"}, manual: true)
iex> tool.manual
true