Packages
nous
0.12.17
0.17.0
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.17
0.12.16
0.12.15
0.12.14
0.12.13
0.12.12
0.12.11
0.12.9
0.12.7
0.12.6
0.12.5
0.12.3
0.12.2
0.12.0
0.11.3
0.11.0
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.5.0
AI agent framework for Elixir with multi-provider LLM support
Current section
Files
Jump to
Current section
Files
lib/nous/plugin.ex
defmodule Nous.Plugin do
@moduledoc """
Behaviour for composable agent extensions.
Plugins allow you to extend agent capabilities without modifying the core agent
or creating monolithic behaviour modules. Multiple plugins can be composed together.
## Example
defmodule MyApp.Plugins.Logging do
@behaviour Nous.Plugin
@impl true
def init(_agent, ctx) do
ctx
end
@impl true
def before_request(_agent, ctx, _tools) do
IO.puts("Making LLM request with \#{length(ctx.messages)} messages")
{ctx, []}
end
@impl true
def after_response(_agent, response, ctx) do
IO.puts("Got response: \#{inspect(response.content)}")
ctx
end
end
## Usage
agent = Agent.new("openai:gpt-4",
plugins: [MyApp.Plugins.Logging, Nous.Plugins.TodoTracking]
)
## Callback Execution Order
Plugins are executed in list order. The context flows through each plugin
sequentially for all hooks:
1. `init/2` — once at run start
2. `system_prompt/2` — once, fragments joined into system message
3. `tools/2` — once per iteration, tools collected
4. `before_request/3` — before each LLM call
5. `after_response/3` — after each LLM response
6. `after_run/3` — once after the entire run completes (post-loop)
## Built-in Plugins
- `Nous.Plugins.TodoTracking` - Automatic todo/task management
- `Nous.Plugins.HumanInTheLoop` - Human approval for tool calls
- `Nous.Plugins.Summarization` - Context window management
"""
alias Nous.Agent.Context
@doc """
Initialize the plugin when the agent run starts.
Use this to set up initial state in `ctx.deps`, register callbacks, etc.
Called once at the start of each `Nous.Agent.run/3`.
"""
@callback init(agent :: Nous.Agent.t(), ctx :: Context.t()) :: Context.t()
@doc """
Contribute additional tools for this agent run.
Return a list of `Nous.Tool` structs to add to the agent's tool set.
Called once per iteration before the LLM request.
"""
@callback tools(agent :: Nous.Agent.t(), ctx :: Context.t()) :: [Nous.Tool.t()]
@doc """
Contribute system prompt fragments.
Return a string to append to the system prompt, or nil for no contribution.
Fragments from all plugins are joined with newlines.
"""
@callback system_prompt(agent :: Nous.Agent.t(), ctx :: Context.t()) :: String.t() | nil
@doc """
Pre-process before each LLM call.
Receives the current context and tools list. Return the updated context
and the updated tools list.
"""
@callback before_request(agent :: Nous.Agent.t(), ctx :: Context.t(), tools :: [Nous.Tool.t()]) ::
{Context.t(), [Nous.Tool.t()]}
@doc """
Post-process after each LLM response.
Receives the LLM response message and current context.
Return the updated context.
"""
@callback after_response(
agent :: Nous.Agent.t(),
response :: Nous.Message.t(),
ctx :: Context.t()
) ::
Context.t()
@doc """
Post-process after the entire agent run completes.
Receives the agent, the final result map, and the final context.
Return the updated context. Use this for end-of-run housekeeping
like auto-updating memory.
"""
@callback after_run(agent :: Nous.Agent.t(), result :: map(), ctx :: Context.t()) :: Context.t()
@optional_callbacks [
init: 2,
tools: 2,
system_prompt: 2,
before_request: 3,
after_response: 3,
after_run: 3
]
# Plugin execution helpers
@doc """
Run `init/2` across all plugins, threading context through each.
"""
@spec run_init([module()], Nous.Agent.t(), Context.t()) :: Context.t()
def run_init(plugins, agent, ctx) do
Enum.reduce(plugins, ctx, fn plugin, acc_ctx ->
if exports?(plugin, :init, 2) do
plugin.init(agent, acc_ctx)
else
acc_ctx
end
end)
end
@doc """
Collect tools from all plugins.
"""
@spec collect_tools([module()], Nous.Agent.t(), Context.t()) :: [Nous.Tool.t()]
def collect_tools(plugins, agent, ctx) do
Enum.flat_map(plugins, fn plugin ->
if exports?(plugin, :tools, 2) do
plugin.tools(agent, ctx)
else
[]
end
end)
end
@doc """
Collect system prompt fragments from all plugins.
"""
@spec collect_system_prompts([module()], Nous.Agent.t(), Context.t()) :: String.t() | nil
def collect_system_prompts(plugins, agent, ctx) do
fragments =
plugins
|> Enum.map(fn plugin ->
if exports?(plugin, :system_prompt, 2) do
plugin.system_prompt(agent, ctx)
else
nil
end
end)
|> Enum.reject(&is_nil/1)
case fragments do
[] -> nil
parts -> Enum.join(parts, "\n\n")
end
end
@doc """
Run `before_request/3` across all plugins, threading context and tools.
Each plugin receives the current tools and returns the updated tools list.
"""
@spec run_before_request([module()], Nous.Agent.t(), Context.t(), [Nous.Tool.t()]) ::
{Context.t(), [Nous.Tool.t()]}
def run_before_request(plugins, agent, ctx, tools) do
Enum.reduce(plugins, {ctx, tools}, fn plugin, {acc_ctx, acc_tools} ->
if exports?(plugin, :before_request, 3) do
plugin.before_request(agent, acc_ctx, acc_tools)
else
{acc_ctx, acc_tools}
end
end)
end
@doc """
Run `after_response/3` across all plugins, threading context.
"""
@spec run_after_response([module()], Nous.Agent.t(), Nous.Message.t(), Context.t()) ::
Context.t()
def run_after_response(plugins, agent, response, ctx) do
Enum.reduce(plugins, ctx, fn plugin, acc_ctx ->
if exports?(plugin, :after_response, 3) do
plugin.after_response(agent, response, acc_ctx)
else
acc_ctx
end
end)
end
@doc """
Run `after_run/3` across all plugins, threading context.
"""
@spec run_after_run([module()], Nous.Agent.t(), map(), Context.t()) :: Context.t()
def run_after_run(plugins, agent, result, ctx) do
Enum.reduce(plugins, ctx, fn plugin, acc_ctx ->
if exports?(plugin, :after_run, 3) do
plugin.after_run(agent, result, acc_ctx)
else
acc_ctx
end
end)
end
# Ensure module is loaded before checking exports
defp exports?(module, function, arity) do
Code.ensure_loaded(module)
function_exported?(module, function, arity)
end
end