Compose multiple tool resolvers into one.
Allows consumers to merge base tools (from LlmToolkit.CodeTools) with their own domain-specific tools into a single resolver.
Example
# Base tools + domain tools
resolver = LlmToolkit.Composition.new([
{LlmToolkit.CodeTools, "/project"},
MyApp.DomainTools
])
{:ok, tools} = resolver.available_tools()
# Returns all base tools + domain tools
{:ok, result} = resolver.resolve(%Call{name: "read_file", arguments: %{...}})
# Dispatches to CodeTools
{:ok, result} = resolver.resolve(%Call{name: "search", arguments: %{...}})
# Dispatches to MyApp.DomainToolsPriority
Resolvers are tried in order. First match wins. This means consumer-specific tools can override base tools if needed (e.g., a custom bash tool with sandboxing).
Resolver formats
Each element in the list can be:
- A module implementing
LlmToolkit.ToolResolver(cwd = ".") - A
{module, cwd}tuple for cwd-aware resolvers like CodeTools
Summary
Functions
Returns all available tools from all composed resolvers.
Returns the dispatch recipe for a tool name by checking each resolver.
Creates a composed resolver from a list of resolver specs.
Resolves a tool call by trying each resolver in order.
Types
Functions
@spec available_tools(t()) :: [LlmToolkit.Tool.t()]
Returns all available tools from all composed resolvers.
Tools from earlier resolvers appear first. Duplicate tool names are kept (the first resolver to match a tool name wins during dispatch).
Returns the dispatch recipe for a tool name by checking each resolver.
First non-nil recipe wins.
Creates a composed resolver from a list of resolver specs.
Each spec is either a module (uses "." as cwd) or a {module, cwd} tuple.
@spec resolve(t(), LlmToolkit.Tool.Call.t()) :: {:ok, String.t()} | {:error, String.t()}
Resolves a tool call by trying each resolver in order.
The first resolver to return something other than "Unknown tool" wins.