Current section
Files
Jump to
Current section
Files
lib/codex/tool_guardrail.ex
defmodule Codex.ToolGuardrail do
@moduledoc """
Guardrail applied before or after tool invocation.
"""
defstruct name: nil, stage: :input, handler: nil, run_in_parallel: false, behavior: :allow
@type stage :: :input | :output
@type t :: %__MODULE__{
name: String.t(),
stage: stage(),
handler: function(),
run_in_parallel: boolean(),
behavior: :allow | :reject_content | :raise_exception
}
@doc """
Builds a tool guardrail definition.
"""
@spec new(keyword()) :: t()
def new(opts) when is_list(opts) do
name = Keyword.get(opts, :name, "tool_guardrail")
stage = Keyword.get(opts, :stage, :input)
handler = Keyword.get(opts, :handler)
run_in_parallel = Keyword.get(opts, :run_in_parallel, false)
behavior = Keyword.get(opts, :behavior, :allow)
%__MODULE__{
name: to_string(name),
stage: stage,
handler: handler,
run_in_parallel: run_in_parallel,
behavior: behavior
}
end
@doc """
Runs the guardrail handler for a tool call.
"""
@spec run(t(), map(), term(), map()) ::
:ok | {:reject, String.t()} | {:tripwire, String.t()}
def run(%__MODULE__{handler: handler, behavior: behavior}, event, payload, context)
when is_function(handler, 3) do
normalize_result(handler.(event, payload, context), behavior)
end
def run(%__MODULE__{handler: handler, behavior: behavior}, event, payload, _context)
when is_function(handler, 2) do
normalize_result(handler.(event, payload), behavior)
end
def run(_guardrail, _event, _payload, _context), do: :ok
defp normalize_result(:ok, _behavior), do: :ok
defp normalize_result(:allow, _behavior), do: :ok
defp normalize_result({:ok, _val}, _behavior), do: :ok
defp normalize_result({:allow, _val}, _behavior), do: :ok
defp normalize_result({:reject, message}, behavior),
do: {:reject, message || default_message(behavior)}
defp normalize_result({:reject_content, message}, behavior),
do: {:reject, message || default_message(behavior)}
defp normalize_result({:tripwire, message}, _behavior),
do: {:tripwire, message || "tripwire triggered"}
defp normalize_result({:raise_exception, message}, _behavior),
do: {:tripwire, message || "tripwire triggered"}
defp normalize_result({:deny, message}, behavior),
do: {:reject, message || default_message(behavior)}
defp normalize_result(:deny, behavior), do: {:reject, default_message(behavior)}
defp normalize_result(false, behavior), do: {:reject, default_message(behavior)}
defp normalize_result(_other, _behavior), do: :ok
defp default_message(:reject_content), do: "rejected"
defp default_message(:raise_exception), do: "tripwire triggered"
defp default_message(_), do: "rejected"
end