Skip to content

Latest commit

 

History

History
158 lines (115 loc) · 4.67 KB

File metadata and controls

158 lines (115 loc) · 4.67 KB

Tool Calling With Actions

You want model-selected tool calls with Jido.Action modules, plus deterministic terminal shapes for one-shot and multi-turn runs.

After this guide, you can use:

  • Jido.AI.Actions.ToolCalling.CallWithTools
  • Jido.AI.Actions.ToolCalling.ExecuteTool
  • Jido.AI.Actions.ToolCalling.ListTools

Define A Tool Action

defmodule MyApp.Actions.Multiply do
  use Jido.Action,
    name: "multiply",
    schema: Zoi.object(%{a: Zoi.integer(), b: Zoi.integer()})

  @impl true
  def run(%{a: a, b: b}, _context), do: {:ok, %{product: a * b}}
end

One-Shot Tool Calling (CallWithTools)

One-shot mode returns a terminal turn map without executing tools automatically.

alias Jido.AI.Actions.ToolCalling.CallWithTools

params = %{
  prompt: "What is 6 * 7?",
  tools: ["multiply"]
}

context = %{
  tools: %{"multiply" => MyApp.Actions.Multiply}
}

{:ok, result} = Jido.Exec.run(CallWithTools, params, context)
# result.type == :tool_calls or :final_answer

Auto-Execute Tool Loop (CallWithTools)

Auto-execute mode runs tools and continues until a final answer or max-turn limit.

alias Jido.AI.Actions.ToolCalling.CallWithTools

params = %{
  prompt: "Use multiply to compute 6 * 7 and explain briefly.",
  tools: ["multiply"],
  auto_execute: true,
  max_turns: 5
}

context = %{
  tools: %{"multiply" => MyApp.Actions.Multiply}
}

{:ok, result} = Jido.Exec.run(CallWithTools, params, context)
# result.type == :final_answer when loop completes
# result.turns includes executed loop turns
# result.messages includes assistant/tool conversation messages

Deterministic terminal shapes:

  • completed loop: %{type: :final_answer, text: text, usage: usage, turns: turns, messages: messages, model: model}
  • max turns reached: %{type: :tool_calls, reason: :max_turns_reached, turns: max_turns, usage: usage, model: model}

Direct Tool Execution (ExecuteTool)

Use direct execution when your app already chose the tool and args.

alias Jido.AI.Actions.ToolCalling.ExecuteTool

params = %{
  tool_name: "multiply",
  params: %{a: 6, b: 7}
}

context = %{
  tools: %{"multiply" => MyApp.Actions.Multiply}
}

{:ok, result} = Jido.Exec.run(ExecuteTool, params, context)
# %{tool_name: "multiply", status: :success, result: %{product: 42}}

Tool Discovery And Security Filtering (ListTools)

ListTools defaults to excluding sensitive tool names (include_sensitive: false) and returns only public metadata (name, optional serialized schema).

alias Jido.AI.Actions.ToolCalling.ListTools

context = %{
  tools: %{
    "multiply" => MyApp.Actions.Multiply,
    "admin_delete_user" => MyApp.Actions.AdminDeleteUser
  }
}

{:ok, public_tools} = Jido.Exec.run(ListTools, %{}, context)
{:ok, all_tools} = Jido.Exec.run(ListTools, %{include_sensitive: true}, context)
{:ok, allowlisted} = Jido.Exec.run(ListTools, %{allowed_tools: ["multiply"]}, context)

Security filtering behavior:

  • default denylist filtering by sensitive name fragments (admin, delete, token, secret, etc.)
  • explicit override with include_sensitive: true
  • optional allowlist hard filter with allowed_tools: [...]

Tool Registry Precedence (Tool Map / Context / Plugin State)

CallWithTools, ExecuteTool, and ListTools resolve tool registries with this precedence:

  1. context[:tools]
  2. context[:tool_calling][:tools]
  3. context[:chat][:tools]
  4. context[:state][:tool_calling][:tools]
  5. context[:state][:chat][:tools]
  6. context[:agent][:state][:tool_calling][:tools]
  7. context[:agent][:state][:chat][:tools]
  8. context[:plugin_state][:tool_calling][:tools]
  9. context[:plugin_state][:chat][:tools]

First non-nil registry wins. This keeps behavior deterministic across direct action execution, plugin-routed calls, and fallback context paths.

Dynamic Registration On A Running Agent

{:ok, _agent} = Jido.AI.register_tool(agent_pid, MyApp.Actions.Multiply)
{:ok, true} = Jido.AI.has_tool?(agent_pid, "multiply")

Failure Mode: Tool Execution Returns :not_found

Symptom:

  • ExecuteTool returns tool-not-found error content

Fix:

  • pass a tools map in one of the registry precedence paths above
  • verify module.name/0 matches the requested tool_name

Defaults You Should Know

  • CallWithTools auto_execute default: false
  • CallWithTools max_turns default: 10 (hard-capped by validation)
  • ExecuteTool timeout default: 30_000ms
  • ListTools schema inclusion default: true
  • ListTools sensitive filtering default: enabled (include_sensitive: false)

Next