Nous.Agent.Callbacks (nous v0.13.3)

View Source

Callback execution for agent events.

Supports two notification mechanisms:

  1. Map-based callbacks - Functions in the context's callbacks map
  2. Process messages - Messages sent to the context's notify_pid

Callback Events

  • :on_agent_start - Agent run begins
  • :on_llm_new_delta - Streaming text chunk received
  • :on_llm_new_message - Complete LLM response received
  • :on_tool_call - Tool invocation started
  • :on_tool_response - Tool execution completed
  • :on_agent_complete - Agent run finished successfully
  • :on_error - Error occurred during execution

Map-Based Callbacks

Configure callbacks as a map of event handlers:

ctx = Context.new(callbacks: %{
  on_llm_new_delta: fn _event, delta -> IO.write(delta) end,
  on_tool_call: fn _event, call -> IO.inspect(call, label: "Tool") end
})

Callback functions receive (event_name, payload) and their return values are discarded (side-effects only).

Process Messages

For LiveView integration, set notify_pid:

ctx = Context.new(notify_pid: self())
# Will receive messages like:
# {:agent_delta, text}
# {:tool_call, %{id: ..., name: ..., arguments: ...}}
# {:agent_complete, result}

Example

alias Nous.Agent.{Context, Callbacks}

# Execute callback (safe - handles missing callbacks)
Callbacks.execute(ctx, :on_llm_new_delta, "Hello")

# Execute with metadata
Callbacks.execute(ctx, :on_tool_call, %{
  id: "call_123",
  name: "search",
  arguments: %{"query" => "elixir"}
})

Summary

Functions

List of all supported callback events.

Execute a callback for the given event.

Execute multiple events in sequence.

Check if a specific callback is configured.

Check if process notification is enabled.

Add a callback to the context.

Remove a callback from the context.

Set the notification PID.

Convert an event to a process message format.

Types

event()

@type event() ::
  :on_agent_start
  | :on_llm_new_delta
  | :on_llm_new_message
  | :on_tool_call
  | :on_tool_response
  | :on_agent_complete
  | :on_error

payload()

@type payload() :: any()

Functions

events()

@spec events() :: [event()]

List of all supported callback events.

Events

  • :on_agent_start - Payload: %{agent: agent}
  • :on_llm_new_delta - Payload: String.t() (text chunk)
  • :on_llm_new_message - Payload: Message.t()
  • :on_tool_call - Payload: %{id: String.t(), name: String.t(), arguments: map()}
  • :on_tool_response - Payload: %{id: String.t(), name: String.t(), result: any()}
  • :on_agent_complete - Payload: result map
  • :on_error - Payload: error term

Examples

iex> Callbacks.events()
[:on_agent_start, :on_llm_new_delta, :on_llm_new_message, ...]

execute(ctx, event, payload)

@spec execute(Nous.Agent.Context.t(), event(), payload()) :: :ok

Execute a callback for the given event.

This function:

  1. Invokes the map-based callback if present in ctx.callbacks
  2. Sends a process message if ctx.notify_pid is set

Safe to call even if no callbacks are configured.

Parameters

  • ctx - The agent context
  • event - The event name (atom)
  • payload - Event-specific data

Examples

iex> ctx = Context.new(callbacks: %{on_llm_new_delta: fn _, d -> IO.write(d) end})
iex> Callbacks.execute(ctx, :on_llm_new_delta, "Hello")
:ok

iex> ctx = Context.new(notify_pid: self())
iex> Callbacks.execute(ctx, :on_llm_new_delta, "Hello")
iex> receive do {:agent_delta, text} -> text end
"Hello"

execute_many(ctx, events)

@spec execute_many(Nous.Agent.Context.t(), [{event(), payload()}]) :: :ok

Execute multiple events in sequence.

Examples

iex> Callbacks.execute_many(ctx, [
...>   {:on_tool_call, %{id: "1", name: "search"}},
...>   {:on_tool_response, %{id: "1", result: "found"}}
...> ])
:ok

has_callback?(context, event)

@spec has_callback?(Nous.Agent.Context.t(), event()) :: boolean()

Check if a specific callback is configured.

Examples

iex> ctx = Context.new(callbacks: %{on_llm_new_delta: fn _, _ -> :ok end})
iex> Callbacks.has_callback?(ctx, :on_llm_new_delta)
true

iex> ctx = Context.new()
iex> Callbacks.has_callback?(ctx, :on_llm_new_delta)
false

has_notification?(context)

@spec has_notification?(Nous.Agent.Context.t()) :: boolean()

Check if process notification is enabled.

Examples

iex> ctx = Context.new(notify_pid: self())
iex> Callbacks.has_notification?(ctx)
true

iex> ctx = Context.new()
iex> Callbacks.has_notification?(ctx)
false

put_callback(ctx, event, callback)

@spec put_callback(Nous.Agent.Context.t(), event(), function()) ::
  Nous.Agent.Context.t()

Add a callback to the context.

Examples

iex> ctx = Context.new()
iex> ctx = Callbacks.put_callback(ctx, :on_llm_new_delta, fn _, d -> IO.write(d) end)
iex> Callbacks.has_callback?(ctx, :on_llm_new_delta)
true

remove_callback(ctx, event)

@spec remove_callback(Nous.Agent.Context.t(), event()) :: Nous.Agent.Context.t()

Remove a callback from the context.

Examples

iex> ctx = Context.new(callbacks: %{on_llm_new_delta: fn _, _ -> :ok end})
iex> ctx = Callbacks.remove_callback(ctx, :on_llm_new_delta)
iex> Callbacks.has_callback?(ctx, :on_llm_new_delta)
false

set_notify_pid(ctx, pid)

@spec set_notify_pid(Nous.Agent.Context.t(), pid() | nil) :: Nous.Agent.Context.t()

Set the notification PID.

Examples

iex> ctx = Context.new()
iex> ctx = Callbacks.set_notify_pid(ctx, self())
iex> Callbacks.has_notification?(ctx)
true

to_message(event, payload)

@spec to_message(event(), payload()) :: tuple()

Convert an event to a process message format.

This is used internally but exposed for testing.

Message Format

EventMessage
:on_agent_start{:agent_start, payload}
:on_llm_new_delta{:agent_delta, text}
:on_llm_new_message{:agent_message, message}
:on_tool_call{:tool_call, %{id, name, arguments}}
:on_tool_response{:tool_result, %{id, name, result}}
:on_agent_complete{:agent_complete, result}
:on_error{:agent_error, error}

Examples

iex> Callbacks.to_message(:on_llm_new_delta, "Hello")
{:agent_delta, "Hello"}

iex> Callbacks.to_message(:on_tool_call, %{id: "1", name: "search"})
{:tool_call, %{id: "1", name: "search"}}