defmodule Planck.Agent.Tool do @moduledoc """ An executable tool for use in agent turns. Extends `Planck.AI.Tool` with an `execute_fn` — the function called when the LLM requests a tool invocation. The AI tool schema (name, description, parameters) is extracted when building `Planck.AI.Context`; `execute_fn` stays agent-side. """ @typedoc """ The function invoked when the LLM requests this tool. Receives the calling `agent_id`, the tool call `id` (an opaque string from the provider, used to correlate results), and `args` (the JSON-decoded arguments map). Must return `{:ok, result}` or `{:error, reason}` where both values are strings — they are placed directly into the model's context as tool result text. Exceptions and exit signals are caught by the agent and converted to error strings automatically. """ @type execute_fn :: (agent_id :: String.t(), id :: String.t(), args :: map() -> {:ok, String.t()} | {:error, String.t()}) @typedoc """ A fully-specified tool: schema fields understood by the LLM plus the `execute_fn` that runs when the model calls it. - `:name` — identifier the model uses to call the tool; must be unique within an agent's tool set - `:description` — natural-language description sent to the model; quality here directly affects how reliably the model uses the tool - `:parameters` — JSON Schema object describing the accepted arguments - `:execute_fn` — the function called with the agent id, tool call id, and decoded args """ @type t :: %__MODULE__{ name: String.t(), description: String.t(), parameters: map(), execute_fn: execute_fn() } @enforce_keys [:name, :description, :parameters, :execute_fn] defstruct [:name, :description, :parameters, :execute_fn] @doc """ Build a `Planck.Agent.Tool` from keyword options. ## Examples iex> Tool.new( ...> name: "read_file", ...> description: "Read a file", ...> parameters: %{"type" => "object", "properties" => %{"path" => %{"type" => "string"}}, "required" => ["path"]}, ...> execute_fn: fn _agent_id, _id, %{"path" => path} -> File.read(path) end ...> ) %Planck.Agent.Tool{name: "read_file", ...} """ @spec new(keyword()) :: t() def new(opts) do %__MODULE__{ name: opts[:name], description: opts[:description], parameters: opts[:parameters], execute_fn: opts[:execute_fn] } end @doc """ Convert to a `Planck.AI.Tool` for use in `Planck.AI.Context` (drops `execute_fn`). """ @spec to_ai_tool(t()) :: Planck.AI.Tool.t() def to_ai_tool(%__MODULE__{} = tool) do alias Planck.AI.Tool, as: AITool AITool.new( name: tool.name, description: tool.description, parameters: tool.parameters ) end end