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

View Source

Behaviour for agent implementations.

Defines the interface that different agent types must implement, allowing for customized execution logic while sharing common infrastructure.

Implementing an Agent

To create a custom agent type, implement this behaviour:

defmodule MyApp.CustomAgent do
  @behaviour Nous.Agent.Behaviour

  @impl true
  def init_context(agent, ctx) do
    # Add custom initialization
    ctx
  end

  @impl true
  def build_messages(agent, ctx) do
    # Build messages for the LLM
    ctx.messages
  end

  @impl true
  def process_response(agent, message, ctx) do
    # Process LLM response, handle tool calls
    Context.add_message(ctx, message)
  end

  @impl true
  def extract_output(agent, ctx) do
    # Extract final output from context
    {:ok, Context.last_message(ctx).content}
  end

  @impl true
  def get_tools(agent) do
    agent.tools
  end
end

Built-in Implementations

Using with Agent.new

agent = Agent.new("openai:gpt-4",
  behaviour_module: MyApp.CustomAgent,
  tools: [&search/2]
)

Summary

Callbacks

Called after each tool execution.

Called before each LLM request.

Build messages to send to the LLM.

Extract the final output from the context.

Get tools available for this agent run.

Called when the agent encounters an error.

Initialize context before execution begins.

Process a response from the LLM.

Functions

Call a behaviour callback, using default if not implemented.

Get the default behaviour module.

Get the behaviour module for an agent.

Callbacks

after_tool(agent, tool_call, result, ctx)

(optional)
@callback after_tool(
  agent :: Nous.Agent.t(),
  tool_call :: map(),
  result :: any(),
  ctx :: Nous.Agent.Context.t()
) :: Nous.Agent.Context.t()

Called after each tool execution.

Optional callback for tool result processing.

Parameters

  • agent - The agent configuration
  • tool_call - The tool call that was executed
  • result - The tool result
  • ctx - Current context

Returns

Modified context.

before_request(agent, ctx, request_opts)

(optional)
@callback before_request(
  agent :: Nous.Agent.t(),
  ctx :: Nous.Agent.Context.t(),
  request_opts :: keyword()
) :: keyword()

Called before each LLM request.

Optional callback for request-level customization.

Parameters

  • agent - The agent configuration
  • ctx - Current context
  • request_opts - Options for the LLM request

Returns

Modified request options.

build_messages(agent, ctx)

@callback build_messages(agent :: Nous.Agent.t(), ctx :: Nous.Agent.Context.t()) :: [
  Nous.Message.t()
]

Build messages to send to the LLM.

Called at the start of each iteration. Use this to:

  • Add system prompts
  • Format messages for the provider
  • Include tool schemas

Parameters

  • agent - The agent configuration
  • ctx - Current context with messages

Returns

List of messages to send to the model.

Example

@impl true
def build_messages(agent, ctx) do
  system_msg = Message.system(agent.instructions)
  [system_msg | ctx.messages]
end

extract_output(agent, ctx)

@callback extract_output(agent :: Nous.Agent.t(), ctx :: Nous.Agent.Context.t()) ::
  {:ok, any()} | {:error, term()}

Extract the final output from the context.

Called after the execution loop completes. Use this to:

  • Extract the final answer
  • Format structured output
  • Validate the result

Parameters

  • agent - The agent configuration
  • ctx - Final context after execution

Returns

  • {:ok, output} - Successfully extracted output
  • {:error, reason} - Failed to extract valid output

Example

@impl true
def extract_output(_agent, ctx) do
  case Context.last_message(ctx) do
    %Message{role: :assistant, content: content} ->
      {:ok, content}
    _ ->
      {:error, :no_output}
  end
end

get_tools(agent)

@callback get_tools(agent :: Nous.Agent.t()) :: [Nous.Tool.t()]

Get tools available for this agent run.

Called to determine which tools to advertise to the model. Implementations may add agent-specific tools (e.g., ReAct tools).

Parameters

  • agent - The agent configuration

Returns

List of Tool structs.

Example

@impl true
def get_tools(agent) do
  react_tools = [plan_tool(), note_tool(), final_answer_tool()]
  react_tools ++ agent.tools
end

handle_error(agent, error, ctx)

(optional)
@callback handle_error(
  agent :: Nous.Agent.t(),
  error :: term(),
  ctx :: Nous.Agent.Context.t()
) ::
  {:retry, Nous.Agent.Context.t()}
  | {:continue, Nous.Agent.Context.t()}
  | {:error, term()}

Called when the agent encounters an error.

Optional callback for custom error handling. Default behavior is to return the error.

Parameters

  • agent - The agent configuration
  • error - The error that occurred
  • ctx - Current context when error occurred

Returns

  • {:retry, ctx} - Retry the current iteration
  • {:continue, ctx} - Continue with modified context
  • {:error, reason} - Propagate error

init_context(agent, ctx)

(optional)
@callback init_context(agent :: Nous.Agent.t(), ctx :: Nous.Agent.Context.t()) ::
  Nous.Agent.Context.t()

Initialize context before execution begins.

Called once at the start of Nous.run/3. Use this to:

  • Add agent-specific tools (e.g., ReAct tools)
  • Set up initial state in deps
  • Configure callbacks

Parameters

  • agent - The agent configuration
  • ctx - Initial context from run options

Returns

Modified context for execution.

Example

@impl true
def init_context(agent, ctx) do
  # Add ReAct-specific tools
  ctx
  |> Context.merge_deps(%{tool_history: []})
end

process_response(agent, message, ctx)

@callback process_response(
  agent :: Nous.Agent.t(),
  message :: Nous.Message.t(),
  ctx :: Nous.Agent.Context.t()
) :: Nous.Agent.Context.t()

Process a response from the LLM.

Called after each model response. Use this to:

  • Handle tool calls
  • Update context state
  • Set needs_response based on whether more work is needed

Parameters

  • agent - The agent configuration
  • message - The LLM's response message
  • ctx - Current context

Returns

Updated context. The runner will check ctx.needs_response to decide whether to continue the loop.

Example

@impl true
def process_response(agent, message, ctx) do
  ctx = Context.add_message(ctx, message)

  if Message.has_tool_calls?(message) do
    # Execute tools and add results
    execute_tools_and_continue(ctx, message.tool_calls)
  else
    # No tool calls - we're done
    Context.set_needs_response(ctx, false)
  end
end

Functions

call(module, callback, args, default)

@spec call(module(), atom(), list(), any()) :: any()

Call a behaviour callback, using default if not implemented.

Examples

iex> Behaviour.call(MyAgent, :init_context, [agent, ctx], ctx)
# Returns result of MyAgent.init_context(agent, ctx) or ctx if not implemented

default_module()

@spec default_module() :: module()

Get the default behaviour module.

Examples

iex> Behaviour.default_module()
Nous.Agents.BasicAgent

get_module(agent)

@spec get_module(Nous.Agent.t()) :: module()

Get the behaviour module for an agent.

Returns the configured behaviour module, or the default BasicAgent.

Examples

iex> agent = Agent.new("openai:gpt-4")
iex> Behaviour.get_module(agent)
Nous.Agents.BasicAgent

iex> agent = Agent.new("openai:gpt-4", behaviour_module: MyAgent)
iex> Behaviour.get_module(agent)
MyAgent