Nous.Tool.Behaviour behaviour (nous v0.13.3)

View Source

Behaviour for tool implementations.

Allows tools to be defined as modules for better testability and organization. Module-based tools can inject dependencies via the context, making them easy to test with mocks.

Example

defmodule MyApp.Tools.Search do
  @behaviour Nous.Tool.Behaviour

  @impl true
  def metadata do
    %{
      name: "search",
      description: "Search the web for information",
      parameters: %{
        "type" => "object",
        "properties" => %{
          "query" => %{
            "type" => "string",
            "description" => "The search query"
          }
        },
        "required" => ["query"]
      }
    }
  end

  @impl true
  def execute(ctx, %{"query" => query}) do
    # Inject http_client via deps for testing
    http_client = ctx.deps[:http_client] || Nous.HTTP
    api_key = ctx.deps[:search_api_key]

    case http_client.get("https://api.search.com", query: query, key: api_key) do
      {:ok, results} -> {:ok, format_results(results)}
      {:error, _} = err -> err
    end
  end

  defp format_results(results), do: Enum.map(results, & &1["title"])
end

Testing

defmodule MyApp.Tools.SearchTest do
  use ExUnit.Case

  test "search returns formatted results" do
    mock_http = %{
      get: fn _url, _opts ->
        {:ok, [%{"title" => "Result 1"}, %{"title" => "Result 2"}]}
      end
    }

    ctx = Nous.RunContext.new(%{http_client: mock_http, search_api_key: "test"})

    assert {:ok, ["Result 1", "Result 2"]} =
      MyApp.Tools.Search.execute(ctx, %{"query" => "elixir"})
  end
end

Usage with Agent

agent = Nous.Agent.new("openai:gpt-4",
  tools: [Nous.Tool.from_module(MyApp.Tools.Search)]
)

Summary

Callbacks

Execute the tool with context and arguments.

Return tool metadata (name, description, parameters).

Return the tool's schema definition for introspection.

Functions

Get metadata from a module, using defaults if metadata/0 is not implemented.

Check if a module implements the Tool.Behaviour.

Convert a module name to a tool name.

Callbacks

execute(ctx, args)

@callback execute(ctx :: Nous.RunContext.t(), args :: map()) ::
  {:ok, any()} | {:ok, any(), Nous.Tool.ContextUpdate.t()} | {:error, term()}

Execute the tool with context and arguments.

The context provides access to dependencies and execution metadata. Arguments are a map of the parameters passed by the LLM.

Return Values

  • {:ok, result} - Success with the result to return to the LLM
  • {:ok, result, context_update} - Success with context updates (see Nous.Tool.ContextUpdate)
  • {:error, reason} - Failure with error reason

metadata()

(optional)
@callback metadata() :: map()

Return tool metadata (name, description, parameters).

This callback is optional. If not implemented, the tool name will be derived from the module name.

Return Value

%{
  name: "tool_name",
  description: "What the tool does",
  parameters: %{
    "type" => "object",
    "properties" => %{...},
    "required" => [...]
  }
}

schema()

(optional)
@callback schema() :: map()

Return the tool's schema definition for introspection.

This callback is optional. When using Nous.Tool.Schema, it is generated automatically as __tool_schema__/0.

Return Value

%{
  name: "tool_name",
  description: "...",
  category: :read,
  tags: [:file],
  params: [
    %{name: :path, type: :string, required: true, doc: "..."},
    ...
  ]
}

Functions

get_metadata(module)

@spec get_metadata(module()) :: map()

Get metadata from a module, using defaults if metadata/0 is not implemented.

implements?(module)

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

Check if a module implements the Tool.Behaviour.

module_to_name(module)

@spec module_to_name(module()) :: String.t()

Convert a module name to a tool name.

Examples

iex> Nous.Tool.Behaviour.module_to_name(MyApp.Tools.SearchDatabase)
"search_database"