ToolCall (ToolCall v0.1.0)
Copy MarkdownDerive LLM tool-call schemas from @spec and @doc.
defmodule MyApp.Tools.Weather do
use ToolCall
@doc """
Get the forecast for a city.
## Parameters
* `city` - city name
* `days` - how many days ahead
"""
@spec get_weather(city :: String.t(), days :: pos_integer() | nil) ::
{:ok, %{temp: float()}} | {:error, String.t()}
def get_weather(city, days \\ 3)
end
ToolCall.emit(MyApp.Tools.Weather, :openai)
ToolCall.run(MyApp.Tools.Weather, :get_weather, %{"city" => "Shanghai"})Everything is generated at compile time from module attributes. No runtime
path reads the Docs or Dbgi chunks, because release builds strip them.
Visible local @typedoc text is recovered in a second compile phase
(@after_compile) and embedded in a generated companion module; runtime code
never calls Code.fetch_docs/1. The companion is ordinary compiled code,
loaded on first use like any other module.
Supported typespec subset
See docs/design.md. @type, @typep and @opaque declarations in the same
module are resolved before mapping, including parameterised ones. Unknown
types degrade to {} (any value) rather than failing compilation unless a
ToolCall.TypeProvider registered with use ToolCall, types: [...] resolves
them; types that JSON cannot express at all (functions, pids, refs) are
flagged with a compile-time warning.
Summary
Functions
Returns a provider-neutral description of every tool in module.
Emits schemas for target (:openai or :anthropic).
Runs a tool by name. args uses string keys, as decoded from JSON.
Returns the tool names derived from a module, in declaration order.
Returns the tools derived from a module.
Functions
@spec describe(module(), keyword()) :: [ %{ module: module(), name: String.t(), description: String.t(), parameters: map() } ]
Returns a provider-neutral description of every tool in module.
Each entry has :module, :name, :description and :parameters, where
:parameters is the JSON Schema object for the arguments with no provider
envelope around it. Use this when your client owns the request format; use
emit/3 when you want the OpenAI or Anthropic envelope.
Options
:strict- render the strict JSON Schema subset (seeemit/3).
@spec emit( module() | [ToolCall.IR.Tool.t()] | ToolCall.IR.Tool.t(), :openai | :anthropic, keyword() ) :: map() | [map()]
Emits schemas for target (:openai or :anthropic).
For a module the supported variants are rendered at compile time and
embedded as a literal, so this is a direct or guarded map lookup and strict
may be a runtime boolean. A module returns a list with one schema per tool; a
single ToolCall.IR.Tool returns one schema map and a list of them a list,
rendered on demand, which is what dynamically built tools need. Runtime IR is
validated before rendering; invalid types raise ArgumentError.
Options
:strict- OpenAI strict mode. The tool object carries"strict": true, every property becomes required, optional values become nullable and every object is closed, which is the only shape the API accepts whenstrict: trueis set. Ignored for:anthropic.
Runs a tool by name. args uses string keys, as decoded from JSON.
Returns the tool names derived from a module, in declaration order.
@spec tools(module()) :: [ToolCall.IR.Tool.t()]
Returns the tools derived from a module.
The embedded IR keeps Type.source, which charlist handling needs at
runtime, and drops the spec and parameter ASTs, which are compile-time
provenance only.