PtcRunner.Tool (PtcRunner v0.7.0)

Copy Markdown View Source

Normalized tool definition for PTC-Lisp and SubAgent.

Tools can be defined in multiple formats and are normalized to this struct. Supports function references, explicit signatures, and introspection.

Tool Type

Tools can be one of three types:

  • :native - Elixir function
  • :llm - LLM-powered tool (SubAgent only)
  • :subagent - SubAgent wrapped as tool (SubAgent only)

Tool Formats

All tool formats are accepted and normalized internally. Common patterns:

1. Function reference (extracts @spec and @doc)

"get_user" => &MyApp.get_user/1

2. Function with explicit signature

"search" => {&MyApp.search/2, "(query :string, limit :int) -> [{id :int}]"}

3. Function with signature and description

"analyze" => {&MyApp.analyze/1,
  signature: "(data :map) -> {score :float}",
  description: "Analyze data and return anomaly score"
}

4. Anonymous function

"get_time" => fn _args -> DateTime.utc_now() end

5. Skip validation explicitly

"dynamic" => {&MyApp.dynamic/1, :skip}

Type Definition

%PtcRunner.Tool{
  name: "get_user",
  function: &MyApp.get_user/1,
  signature: "(id :int) -> {id :int, name :string}",
  description: "Get user by ID",
  type: :native
}

Field Reference

  • name - Tool name as string (required)
  • function - Callable (required for native tools)
  • signature - Optional signature for validation: "(inputs) -> outputs"
  • description - Optional description for LLM visibility
  • type - Tool type: :native, :llm, :subagent
  • cache - Enable result caching by {tool_name, args} (default: false)

Result Caching

Set cache: true on tools with stable, pure outputs — identical inputs always produce the same result. Cached results persist across turns within a single SubAgent.run/2 call.

"get-config" => {&MyApp.get_config/1,
  signature: "(key :string) -> :any",
  cache: true
}

Do not use cache: true on tools that read mutable state modifiable by other tools in the session, as the cache has no automatic invalidation.

In pmap, two parallel branches may both miss the cache and execute — last-write-wins on merge. Only successful results are cached; errors are never stored.

Summary

Functions

Creates a normalized Tool struct from a name and format.

Types

t()

@type t() :: %PtcRunner.Tool{
  cache: boolean(),
  description: String.t() | nil,
  function: (map() -> term()) | nil,
  name: String.t(),
  signature: String.t() | nil,
  type: :native | :llm | :subagent
}

tool_format()

@type tool_format() ::
  (map() -> term())
  | {(map() -> term()), String.t()}
  | {(map() -> term()), keyword()}
  | {(map() -> term()), :skip}

Functions

new(name, format)

@spec new(String.t(), tool_format()) :: {:ok, t()} | {:error, term()}

Creates a normalized Tool struct from a name and format.

Handles multiple input formats and normalizes to a consistent structure. Attempts to extract @spec and @doc from bare function references.

Parameters

  • name - Tool name as string
  • format - One of: function, {function, signature}, {function, options}, :skip

Returns

{:ok, tool} on success, {:error, reason} on failure.

Examples

Simple function reference (auto-extracts @doc and @spec if available):

iex> {:ok, tool} = PtcRunner.Tool.new("get_time", fn _args -> DateTime.utc_now() end)
iex> tool.name
"get_time"
iex> tool.type
:native

Function with explicit signature:

iex> {:ok, tool} = PtcRunner.Tool.new("search", {fn _args -> [] end, "(query :string, limit :int) -> [{id :int}]"})
iex> tool.signature
"(query :string, limit :int) -> [{id :int}]"

Function with signature and description:

iex> {:ok, tool} = PtcRunner.Tool.new("analyze", {fn _args -> %{} end,
...>   signature: "(data :map) -> {score :float}",
...>   description: "Analyze data and return anomaly score"
...> })
iex> tool.description
"Analyze data and return anomaly score"

Skip validation:

iex> {:ok, tool} = PtcRunner.Tool.new("dynamic", {fn _args -> nil end, :skip})
iex> tool.signature
nil