Current section
Files
Jump to
Current section
Files
lib/adk/agent/custom_agent.ex
defmodule ADK.Agent.CustomAgent do
@moduledoc """
A custom agent built from an `ADK.Agent.Config`.
Wraps a user-provided run function with before/after callback hooks.
If a before callback returns non-nil Content, the run function is skipped
and an event with that content is emitted instead.
"""
@behaviour ADK.Agent
alias ADK.Agent.{CallbackContext, Config, InvocationContext}
alias ADK.Event
alias ADK.Types.Content
@type t :: %__MODULE__{config: Config.t()}
defstruct [:config]
@doc "Creates a new custom agent from a Config."
@spec new(Config.t()) :: t()
def new(%Config{} = config), do: %__MODULE__{config: config}
@impl ADK.Agent
def name(%__MODULE__{config: config}), do: config.name
@impl ADK.Agent
def description(%__MODULE__{config: config}), do: config.description
@impl ADK.Agent
def sub_agents(%__MODULE__{config: config}), do: config.sub_agents
@impl ADK.Agent
def run(%__MODULE__{config: config} = _agent, %InvocationContext{} = ctx) do
Stream.resource(
fn -> {:before, ctx, config} end,
&next/1,
fn _ -> :ok end
)
end
defp next({:before, ctx, config}) do
cb_ctx = CallbackContext.new(ctx)
case run_before_callbacks(config.before_agent_callbacks, cb_ctx) do
{:short_circuit, content, cb_ctx} ->
event = make_callback_event(ctx, content, cb_ctx)
{[event], :done}
{:continue, _cb_ctx} ->
if config.run do
inner_stream = config.run.(ctx)
{[], {:stream, Enum.to_list(inner_stream), ctx, config}}
else
{[], {:after, ctx, config}}
end
end
end
defp next({:stream, [], ctx, config}) do
{[], {:after, ctx, config}}
end
defp next({:stream, [event | rest], ctx, config}) do
event = maybe_set_author(event, config.name)
{[event], {:stream, rest, ctx, config}}
end
defp next({:after, ctx, config}) do
cb_ctx = CallbackContext.new(ctx)
case run_after_callbacks(config.after_agent_callbacks, cb_ctx) do
{:short_circuit, content, cb_ctx} ->
event = make_callback_event(ctx, content, cb_ctx)
{[event], :done}
{:continue, _cb_ctx} ->
{[], :done}
end
end
defp next(:done) do
{:halt, :done}
end
defp run_before_callbacks([], cb_ctx), do: {:continue, cb_ctx}
defp run_before_callbacks([callback | rest], cb_ctx) do
case callback.(cb_ctx) do
{%Content{} = content, updated_ctx} -> {:short_circuit, content, updated_ctx}
{nil, updated_ctx} -> run_before_callbacks(rest, updated_ctx)
end
end
defp run_after_callbacks([], cb_ctx), do: {:continue, cb_ctx}
defp run_after_callbacks([callback | rest], cb_ctx) do
case callback.(cb_ctx) do
{%Content{} = content, updated_ctx} -> {:short_circuit, content, updated_ctx}
{nil, updated_ctx} -> run_after_callbacks(rest, updated_ctx)
end
end
defp make_callback_event(ctx, content, cb_ctx) do
Event.new(
invocation_id: ctx.invocation_id,
branch: ctx.branch,
author: if(ctx.agent, do: name(ctx.agent), else: nil),
content: content,
actions: cb_ctx.actions
)
end
defp maybe_set_author(%Event{author: nil} = event, name), do: %{event | author: name}
defp maybe_set_author(event, _name), do: event
end