WardrikCore.Tool (wardrik_core v0.1.0-rc.1)

Copy Markdown

A tool an agent may call.

WardrikCore.Tool.new!(
  name: "order_status",
  description: "Say where an order is.",
  schema: [order_id: [type: :string, required: true]],
  run: fn %{order_id: id}, _context -> {:ok, MyApp.Orders.status(id)} end,
  annotations: [read_only: true]
)

Return values

run takes the checked arguments and the WardrikCore.Context, and returns:

  • {:ok, answer} or {:error, reason}.
  • {:ok, answer, context} or {:error, reason, context} - with the context changed, to report on the step. A call that failed after paying returns the error form.
  • {:park, reason} - waits for a person.

For example, to report what a paid API cost:

run: fn %{email: email}, context ->
  found = Enrich.lookup(email)
  {:ok, found, WardrikCore.Context.measure(context, :usd, 0.01)}
end

Labels

An answer can carry labels in its metadata, such as "outside" for text nobody on the agent's side wrote. The run carries the label from then on. See WardrikCore.Context.labels/1.

{:ok, %ReqLLM.ToolResult{output: page, metadata: %{labels: ["outside"]}}}

Annotations

Facts about a tool that any package can read. Give them as :annotations to new!/1, or with annotate/2.

  • :read_only - it changes nothing.
  • :destructive - what it changes can't be undone.
  • :idempotent - a call is safe to repeat, such as a payment with an idempotency key.
  • :open_world - it reaches things outside the agent's own systems, such as the web.
  • :timeout - how long a call may take, in milliseconds.
  • :resources - what a call acts on, as a function of its arguments, such as fn args -> [args["path"]] end.
  • :labels - labels every answer carries, such as ["outside"] for a web search.

The first four are MCP's tool annotations. A tool that doesn't set them has none of them.

A call to a tool that's read_only or idempotent is retried when it fails, and repeated when it's lost in a crash. Any other call lost that way parks as :in_doubt until a person answers. See repeatable?/1.

Summary

Functions

Adds annotations, such as read_only: true. Replaces any with the same key.

Returns the annotation key, or default if the tool doesn't set it.

Runs the tool call names, after checking its arguments against the tool's schema.

Returns the private value under key, or default.

Builds a tool.

Puts value in the private data under key. Use your own module as the key.

Returns whether a call to the tool is safe to repeat: it's read_only or idempotent.

Updates the private value under key with fun. Puts default if there's none yet.

Types

answer()

@type answer() ::
  {:ok, term()}
  | {:ok, term(), WardrikCore.Context.t()}
  | {:error, term()}
  | {:error, term(), WardrikCore.Context.t()}
  | {:park, term()}

t()

@type t() :: %WardrikCore.Tool{
  annotations: %{optional(atom()) => term()},
  description: String.t(),
  name: String.t(),
  private: map(),
  req_llm: ReqLLM.Tool.t(),
  run: (map(), WardrikCore.Context.t() -> answer()),
  schema: keyword() | map()
}

Functions

annotate(tool, new)

@spec annotate(t(), keyword() | map()) :: t()

Adds annotations, such as read_only: true. Replaces any with the same key.

annotation(tool, key, default \\ nil)

@spec annotation(t(), atom(), term()) :: term()

Returns the annotation key, or default if the tool doesn't set it.

call(tools, call, context)

Runs the tool call names, after checking its arguments against the tool's schema.

Returns {:error, reason} if no tool has that name or the arguments don't match.

get_private(struct, key, default \\ nil)

@spec get_private(t(), term(), term()) :: term()

Returns the private value under key, or default.

new!(opts)

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

Builds a tool.

Options

  • :name - required.
  • :description - required. What the model reads to decide when to call it.
  • :run - required. A 2-arity function of the arguments and the context.
  • :schema - the arguments, as a NimbleOptions-style keyword list or a JSON Schema map. [] unless set.
  • :annotations - see above.

Raises if a required option is missing.

put_private(struct, key, value)

@spec put_private(t(), term(), term()) :: t()

Puts value in the private data under key. Use your own module as the key.

repeatable?(tool)

@spec repeatable?(t()) :: boolean()

Returns whether a call to the tool is safe to repeat: it's read_only or idempotent.

update_private(struct, key, default, fun)

@spec update_private(t(), term(), term(), (term() -> term())) :: t()

Updates the private value under key with fun. Puts default if there's none yet.