Rho.Tool behaviour (rho v0.1.0)

Copy Markdown View Source

A tool the agent can call. Implementations are plain modules, so a host can register one at runtime by module, or serve changing sets of them from a source (see Rho.ToolRegistry).

run/2 gets the decoded JSON arguments (string keys) and a context map (:session_id, :call_id, :data_dir; from a loop also :loop, its pid, and :withheld, the tool names that loop doesn't offer, so read it with Map.get(ctx, :withheld, [])). It returns {:ok, output} or {:error, reason}; non-binary values are JSON-encoded (or inspected) for the model. Pictures, and data for the host's screens beside the text ({:ok, {:details, output, details}}), are Rho.ToolRunner's. A raise, exit or timeout becomes an error result; it never takes the loop down.

Untrusted tools. The loop reads a tool's declaration (name/0, description/0, parameter_schema/0, timeout_ms/0, concurrency/0) in its own process. A Dyn tool's (Rho.Untrusted.untrusted?/1) is read instead in one throwaway process with a time and heap bound, checked to be plain data and kept for the module's life: one whose name, description or schema raises or isn't a string / JSON Schema map is left out of what the loop offers (logged once), and a bad timeout_ms/0 or concurrency/0 means the default. A read that runs over (a busy phone) leaves the tool out for now and is tried again after 1, 5 and 30 minutes; after that it stays out.

Summary

Types

A tool's declaration: its callbacks' answers, checked.

Callbacks

How a call shares its turn with the reply's other calls (omp's tool concurrency). :parallel (the default) runs alongside them; :exclusive is for a tool that acts on or reads something only one call may touch at a time (the front's one screen): a turn with any exclusive call runs all its calls one at a time, in the model's order.

JSON Schema (string keys) of the arguments object.

Checks the tool works, without side effects outside a temp dir.

Per-call timeout; default default_timeout_ms/0.

Functions

module's concurrency/0, or :parallel. The loop asks it in its own process, so anything but :exclusive, a raise included, is :parallel.

The per-call timeout of a tool without timeout_ms/0 (30 000 ms).

module's per-call timeout: its timeout_ms/0, else default_timeout_ms/0. A Dyn tool's comes from its checked declaration.

The req_llm declaration sent to the model, nil for an untrusted tool whose declaration failed (it isn't offered). Execution never goes through req_llm's callback: the loop runs tools itself (in parallel, gated, with timeouts).

Is module a loaded tool (exports the required callbacks)?

Types

info()

@type info() :: %{
  name: String.t(),
  description: String.t(),
  parameter_schema: map(),
  timeout_ms: pos_integer(),
  concurrency: :parallel | :exclusive
}

A tool's declaration: its callbacks' answers, checked.

Callbacks

concurrency()

(optional)
@callback concurrency() :: :parallel | :exclusive

How a call shares its turn with the reply's other calls (omp's tool concurrency). :parallel (the default) runs alongside them; :exclusive is for a tool that acts on or reads something only one call may touch at a time (the front's one screen): a turn with any exclusive call runs all its calls one at a time, in the model's order.

description()

@callback description() :: String.t()

name()

@callback name() :: String.t()

parameter_schema()

@callback parameter_schema() :: map()

JSON Schema (string keys) of the arguments object.

run(args, ctx)

@callback run(args :: map(), ctx :: map()) :: {:ok, term()} | {:error, term()}

selftest()

(optional)
@callback selftest() :: :ok | {:error, term()}

Checks the tool works, without side effects outside a temp dir.

timeout_ms()

(optional)
@callback timeout_ms() :: pos_integer()

Per-call timeout; default default_timeout_ms/0.

Functions

concurrency(module)

@spec concurrency(module()) :: :parallel | :exclusive

module's concurrency/0, or :parallel. The loop asks it in its own process, so anything but :exclusive, a raise included, is :parallel.

default_timeout_ms()

@spec default_timeout_ms() :: pos_integer()

The per-call timeout of a tool without timeout_ms/0 (30 000 ms).

timeout_ms(module)

@spec timeout_ms(module()) :: pos_integer()

module's per-call timeout: its timeout_ms/0, else default_timeout_ms/0. A Dyn tool's comes from its checked declaration.

to_req_llm(module)

@spec to_req_llm(module()) :: ReqLLM.Tool.t() | nil

The req_llm declaration sent to the model, nil for an untrusted tool whose declaration failed (it isn't offered). Execution never goes through req_llm's callback: the loop runs tools itself (in parallel, gated, with timeouts).

tool?(module)

@spec tool?(module()) :: boolean()

Is module a loaded tool (exports the required callbacks)?