Current section
Files
Jump to
Current section
Files
lib/codex/agent.ex
defmodule Codex.Agent do
@moduledoc """
Defines a reusable agent with instructions, tools, and hooks.
"""
@enforce_keys []
defstruct name: nil,
instructions: nil,
prompt: nil,
handoff_description: nil,
handoffs: [],
tools: [],
tool_use_behavior: :run_llm_again,
reset_tool_choice: true,
input_guardrails: [],
output_guardrails: [],
tool_input_guardrails: [],
tool_output_guardrails: [],
hooks: nil,
model: nil,
model_settings: nil
@invalid_field_tags %{
name: :invalid_name,
instructions: :invalid_instructions,
handoff_description: :invalid_handoff_description,
handoffs: :invalid_handoffs,
tools: :invalid_tools,
reset_tool_choice: :invalid_reset_tool_choice,
input_guardrails: :invalid_input_guardrails,
output_guardrails: :invalid_output_guardrails,
tool_input_guardrails: :invalid_tool_input_guardrails,
tool_output_guardrails: :invalid_tool_output_guardrails,
model: :invalid_model
}
@type t :: %__MODULE__{
name: String.t() | nil,
instructions: String.t() | nil,
prompt: map() | String.t() | nil,
handoffs: list(),
tools: list(),
handoff_description: String.t() | nil,
tool_use_behavior:
:run_llm_again
| :stop_on_first_tool
| %{optional(:stop_at_tool_names) => [String.t()]}
| function()
| nil,
reset_tool_choice: boolean(),
input_guardrails: list(),
output_guardrails: list(),
tool_input_guardrails: list(),
tool_output_guardrails: list(),
hooks: term(),
model: String.t() | nil,
model_settings: map() | struct() | nil
}
@doc """
Builds a validated `%Codex.Agent{}` struct.
"""
@spec new(map() | keyword() | t()) :: {:ok, t()} | {:error, term()}
def new(%__MODULE__{} = agent), do: {:ok, agent}
def new(attrs) when is_list(attrs), do: attrs |> Map.new() |> new()
def new(attrs) when is_map(attrs) do
name = Map.get(attrs, :name, Map.get(attrs, "name"))
instructions = Map.get(attrs, :instructions, Map.get(attrs, "instructions"))
prompt = Map.get(attrs, :prompt, Map.get(attrs, "prompt"))
handoff_description =
Map.get(attrs, :handoff_description, Map.get(attrs, "handoff_description"))
handoffs = Map.get(attrs, :handoffs, Map.get(attrs, "handoffs", []))
tools = Map.get(attrs, :tools, Map.get(attrs, "tools", []))
tool_use_behavior =
Map.get(attrs, :tool_use_behavior, Map.get(attrs, "tool_use_behavior", :run_llm_again))
reset_tool_choice =
Map.get(attrs, :reset_tool_choice, Map.get(attrs, "reset_tool_choice", true))
input_guardrails = Map.get(attrs, :input_guardrails, Map.get(attrs, "input_guardrails", []))
output_guardrails =
Map.get(attrs, :output_guardrails, Map.get(attrs, "output_guardrails", []))
tool_input_guardrails =
Map.get(attrs, :tool_input_guardrails, Map.get(attrs, "tool_input_guardrails", []))
tool_output_guardrails =
Map.get(attrs, :tool_output_guardrails, Map.get(attrs, "tool_output_guardrails", []))
hooks = Map.get(attrs, :hooks, Map.get(attrs, "hooks"))
model = Map.get(attrs, :model, Map.get(attrs, "model"))
model_settings = Map.get(attrs, :model_settings, Map.get(attrs, "model_settings"))
with :ok <- validate_optional_string(name, :name),
:ok <- validate_optional_string(instructions, :instructions),
:ok <- validate_prompt(prompt),
:ok <- validate_optional_string(handoff_description, :handoff_description),
{:ok, handoffs} <- ensure_list(handoffs, :handoffs),
{:ok, tools} <- ensure_list(tools, :tools),
{:ok, tool_use_behavior} <- validate_tool_use_behavior(tool_use_behavior),
:ok <- validate_boolean(reset_tool_choice, :reset_tool_choice),
{:ok, input_guardrails} <- ensure_list(input_guardrails, :input_guardrails),
{:ok, output_guardrails} <- ensure_list(output_guardrails, :output_guardrails),
{:ok, tool_input_guardrails} <-
ensure_list(tool_input_guardrails, :tool_input_guardrails),
{:ok, tool_output_guardrails} <-
ensure_list(tool_output_guardrails, :tool_output_guardrails),
:ok <- validate_optional_string(model, :model) do
{:ok,
%__MODULE__{
name: name,
instructions: instructions,
prompt: prompt,
handoff_description: handoff_description,
handoffs: handoffs,
tools: tools,
tool_use_behavior: tool_use_behavior,
reset_tool_choice: reset_tool_choice,
input_guardrails: input_guardrails,
output_guardrails: output_guardrails,
tool_input_guardrails: tool_input_guardrails,
tool_output_guardrails: tool_output_guardrails,
hooks: hooks,
model: model,
model_settings: model_settings
}}
else
{:error, _} = error -> error
end
end
defp validate_optional_string(nil, _field), do: :ok
defp validate_optional_string(value, _field) when is_binary(value), do: :ok
defp validate_optional_string(value, field), do: invalid_field_error(field, value)
defp validate_tool_use_behavior(:auto), do: {:ok, :run_llm_again}
defp validate_tool_use_behavior("auto"), do: {:ok, :run_llm_again}
defp validate_tool_use_behavior(:run_llm_again), do: {:ok, :run_llm_again}
defp validate_tool_use_behavior("run_llm_again"), do: {:ok, :run_llm_again}
defp validate_tool_use_behavior(:stop_on_first_tool), do: {:ok, :stop_on_first_tool}
defp validate_tool_use_behavior("stop_on_first_tool"), do: {:ok, :stop_on_first_tool}
defp validate_tool_use_behavior(%{} = value) do
names = Map.get(value, :stop_at_tool_names) || Map.get(value, "stop_at_tool_names")
cond do
is_list(names) -> {:ok, %{stop_at_tool_names: names}}
is_nil(names) -> {:error, {:invalid_tool_use_behavior, value}}
true -> {:error, {:invalid_tool_use_behavior, value}}
end
end
defp validate_tool_use_behavior(fun) when is_function(fun), do: {:ok, fun}
defp validate_tool_use_behavior(nil), do: {:ok, :run_llm_again}
defp validate_tool_use_behavior(value), do: {:error, {:invalid_tool_use_behavior, value}}
defp validate_prompt(nil), do: :ok
defp validate_prompt(value) when is_map(value), do: :ok
defp validate_prompt(value) when is_binary(value), do: :ok
defp validate_prompt(value), do: {:error, {:invalid_prompt, value}}
defp validate_boolean(value, _field) when is_boolean(value), do: :ok
defp validate_boolean(value, field), do: invalid_field_error(field, value)
defp ensure_list(value, _field) when is_list(value), do: {:ok, value}
defp ensure_list(nil, _field), do: {:ok, []}
defp ensure_list(value, field), do: invalid_field_error(field, value)
defp invalid_field_error(field, value) do
case Map.fetch(@invalid_field_tags, field) do
{:ok, tag} -> {:error, {tag, value}}
:error -> {:error, {:invalid_field, field, value}}
end
end
end