Arcana.Loop.Tools (Arcana v2.0.1)

Copy Markdown View Source

Default tool definitions and execution for Arcana.Loop.

Tools are exposed to the controller LLM as ReqLLM.Tool structs. The callbacks on those structs are placeholders: the loop runner executes tools itself via execute/4 so it can mutate the loop state (accumulate chunks, record history, terminate the loop).

Default toolset

ToolWhat it does
searchSearch the knowledge base. Uses graph data when available.
answerProvide the final answer (terminates the loop).
give_upAdmit defeat and stop (terminates the loop).

Terminating tools (answer and give_up) end the loop. search is the only tool that touches your Repo and the only one that accumulates state.

Query rewriting and decomposition aren't separate tools — they happen inside the search tool's query parameter, guided by the system prompt.

Custom tools

Pass tools: [...] to Loop.run/2 with your own ReqLLM.Tool structs appended to (or replacing) the defaults. Custom tools are invoked via their :callback function when the controller calls them.

The callback is a 1-arity function (args) -> {:ok, text} | {:error, text} where text is the string returned to the controller as the tool result. Custom tools always continue the loop (they never terminate it). The controller can still call answer or give_up to end the loop.

web_search = ReqLLM.Tool.new!(
  name: "web_search",
  description: "Search the web for current information.",
  parameter_schema: [query: [type: :string, required: true]],
  callback: fn %{query: q} ->
    case MyApp.WebSearch.run(q) do
      {:ok, results} -> {:ok, format_results(results)}
      {:error, reason} -> {:error, inspect(reason)}
    end
  end
)

Loop.run(ctx, tools: Tools.default() ++ [web_search], controller_llm: llm)

Summary

Functions

Returns the default list of tool definitions as ReqLLM.Tool structs.

Executes a tool call against the loop context.

Functions

default(collections \\ [nil])

@spec default(collections :: [String.t() | nil]) :: [ReqLLM.Tool.t()]

Returns the default list of tool definitions as ReqLLM.Tool structs.

These are the tools shipped with Arcana.Loop. Pass tools: default/0 (or omit :tools) to use them. Replace with your own list to customize.

Context-aware search tool

When called with a list of collection names longer than one, the search tool gains an optional collection parameter that the controller can use to narrow the search per call. When called with no collections, [nil], or a single-element list, the parameter is omitted and the caller-supplied collection (if any) is always used.

That way, Arcana.Loop.new(collection: "docs") locks the controller to a single collection programmatically (the tool can't even express a different one), while Arcana.Loop.new(collections: ["a", "b"]) lets the controller pick per call.

execute(ctx, name, args, opts \\ [])

@spec execute(Arcana.Loop.Context.t(), String.t(), map(), keyword()) ::
  {:continue, Arcana.Loop.Context.t(), String.t(), map()}
  | {:terminate, Arcana.Loop.Context.t(), atom(), String.t()}

Executes a tool call against the loop context.

Returns one of:

  • {:continue, updated_ctx, summary_text, meta} - the loop should continue; summary_text is what gets sent back to the controller LLM as the tool result, and meta is a map of extra metadata to record in the tool history (e.g. %{returned_chunk_ids: [...]} for search).
  • {:terminate, updated_ctx, reason, answer_text} - the loop should stop; reason is one of :answered or :gave_up; answer_text becomes ctx.answer.