Documentation
¶
Overview ¶
Package guardrails provides built-in ProviderHook implementations that bridge the unified eval system to the pipeline's hook infrastructure.
Index ¶
- Constants
- Variables
- func CompileValidators(validators []prompt.ValidatorConfig) ([]hooks.ProviderHook, error)
- func CompileValidatorsWithOptions(validators []prompt.ValidatorConfig, registry *evals.EvalTypeRegistry, ...) ([]hooks.ProviderHook, error)
- func CompileValidatorsWithRegistry(validators []prompt.ValidatorConfig, registry *evals.EvalTypeRegistry) ([]hooks.ProviderHook, error)
- func NewGuardrailHook(typeName string, params map[string]any, opts ...GuardrailOption) (hooks.ProviderHook, error)
- func NewGuardrailHookFromRegistry(typeName string, params map[string]any, registry *evals.EvalTypeRegistry, ...) (hooks.ProviderHook, error)
- func ValidatorsToHooks(validators []prompt.ValidatorConfig) []hooks.ProviderHookdeprecated
- func ValidatorsToHooksWithRegistry(validators []prompt.ValidatorConfig, registry *evals.EvalTypeRegistry) []hooks.ProviderHookdeprecated
- type GuardrailHookAdapter
- func (a *GuardrailHookAdapter) AfterCall(ctx context.Context, req *hooks.ProviderRequest, resp *hooks.ProviderResponse) hooks.Decision
- func (a *GuardrailHookAdapter) BeforeCall(ctx context.Context, req *hooks.ProviderRequest) hooks.Decision
- func (a *GuardrailHookAdapter) Name() string
- func (a *GuardrailHookAdapter) OnChunk(ctx context.Context, chunk *providers.StreamChunk) hooks.Decision
- func (a *GuardrailHookAdapter) SetEmitter(e *events.Emitter)
- type GuardrailOption
- type Spec
- func Input(evalType string, params map[string]any, opts ...GuardrailOption) Spec
- func InputFunc(name string, fn func(context.Context, *hooks.InputRequest) hooks.Decision) Spec
- func Output(evalType string, params map[string]any, opts ...GuardrailOption) Spec
- func OutputFunc(name string, fn func(context.Context, *hooks.OutputRequest) hooks.Decision) Spec
Constants ¶
const ( DirectionInput = hooks.DirectionInput DirectionOutput = hooks.DirectionOutput DirectionBoth = hooks.DirectionBoth )
Aliases of the canonical direction constants, retained for existing callers. See runtime/hooks for what each value means; the vocabulary lives there so the pipeline stage need not import a concrete hook implementation to tag a firing.
Variables ¶
var ErrEmptySpec = errors.New(
"uninitialized Spec — build it with Input, Output, InputFunc or OutputFunc")
ErrEmptySpec is returned by Spec.Hook for a zero-value Spec — one that never went through Input, Output, InputFunc or OutputFunc. Reachable from a pre-sized slice (make([]guardrails.Spec, n)) whose entries a branch failed to assign; returning an error keeps that config mistake out of the panic path.
var ErrGuardrailNeedsJudge = errors.New("guardrail needs a judge provider")
ErrGuardrailNeedsJudge is returned when a validator names a judge-backed eval type and no judge provider was supplied. It is fatal on the strict path for the same reason an unknown type is: the guardrail cannot run.
Before this, the two judge-backed families failed in opposite directions and both silently — a `toxicity` guardrail scored 0.0 against a 1.0 floor and blocked EVERY turn while reporting a content violation, and `pii_leakage` degraded open so its LLM layer never ran at all (#1996). Neither is detectable without reading the blocked message, and both are decided long before any turn: the pack declares the judge it needs, the host supplies it, and if it did not, that is knowable at load.
var ErrInvalidGuardrailParams = errors.New("invalid guardrail params")
ErrInvalidGuardrailParams is returned when the eval type IS registered and its own evals.ParamValidator rejects the params. Like ErrUnknownGuardrailType it is fatal on the strict path: the handler is the authority on its own config, so its rejection is a statement that this declaration cannot work — not a param a newer runtime might understand. See CompileValidators.
var ErrUnknownGuardrailType = errors.New("unknown guardrail type")
ErrUnknownGuardrailType is returned when a validator names an eval type that is not registered. It is distinguishable from other construction failures because it is treated as fatal: an unknown type has no legitimate use, so dropping it would leave a conversation silently unprotected. See CompileValidators.
Functions ¶
func CompileValidators ¶
func CompileValidators(validators []prompt.ValidatorConfig) ([]hooks.ProviderHook, error)
CompileValidators turns pack-declared validators into ProviderHooks suitable for prepending to a hook registry. Both SDK.Open and Arena's per-turn pipeline use this so guardrails run identically in production and in tests.
Per-validator behavior:
- Validators with Enabled == &false are skipped silently (explicit opt-out). nil Enabled means enabled (spec default).
- All accepted validators enforce: on a hit they rewrite the assistant message in place (truncate or replace). If you want observe-only behavior, declare an eval and assert on it; guardrails always act.
- "message" set on the validator becomes the user-facing blocked text, falling back to Params["message"].
Failure policy — both shapes of "this declaration cannot work" are FATAL, for the same reason:
- An **unknown eval type** returns ErrUnknownGuardrailType. A type that is not registered has no legitimate use — it is a typo — and silently dropping it leaves the conversation with no protection while load appears to succeed. That is fail-open on a safety control.
- A registered type whose own evals.ParamValidator **rejects the params** returns ErrInvalidGuardrailParams. The handler is the authority on its own config; its rejection says this validator cannot run, and dropping it produces exactly the same silently unprotected conversation. This path used to log and skip on a forward-compatibility argument — that a pack authored against a newer runtime may carry params this build does not understand. It does not apply: a build that does not know the type at all is already fatal, and a build that does know it has the handler's own verdict. Forward-compatibility loses to an unprotected conversation.
Params that no handler ever inspects are unaffected — only a handler that implements evals.ParamValidator can reject anything here, and a type with no validator accepts whatever it is given, exactly as before.
On a fatal error no hooks are returned, so a caller cannot accidentally proceed with a partial guardrail set.
func CompileValidatorsWithOptions ¶ added in v2.5.0
func CompileValidatorsWithOptions( validators []prompt.ValidatorConfig, registry *evals.EvalTypeRegistry, opts ...GuardrailOption, ) ([]hooks.ProviderHook, error)
CompileValidatorsWithOptions is CompileValidatorsWithRegistry with options applied to every guardrail it builds — WithJudge above all, which is what makes a judge-backed validator usable at all.
Per-validator options (the blocked message) are applied after these, so a validator's own message still wins.
func CompileValidatorsWithRegistry ¶
func CompileValidatorsWithRegistry( validators []prompt.ValidatorConfig, registry *evals.EvalTypeRegistry, ) ([]hooks.ProviderHook, error)
CompileValidatorsWithRegistry is CompileValidators resolving each validator's eval type against the supplied registry instead of the built-in default. Pass the registry a caller configured (sdk.WithEvalRegistry) so a custom handler can back a pack validator; a nil registry means the default one.
Without this the default registry does not know a custom type, construction fails, and — on the lenient path — the guardrail is logged and dropped, which leaves the conversation unprotected while load appears to succeed (#1717).
Failure policy is CompileValidators's: an unknown type and a handler-rejected param set are both fatal.
func NewGuardrailHook ¶
func NewGuardrailHook(typeName string, params map[string]any, opts ...GuardrailOption) (hooks.ProviderHook, error)
NewGuardrailHook creates a guardrail ProviderHook using the default eval registry.
func NewGuardrailHookFromRegistry ¶
func NewGuardrailHookFromRegistry( typeName string, params map[string]any, registry *evals.EvalTypeRegistry, opts ...GuardrailOption, ) (hooks.ProviderHook, error)
NewGuardrailHookFromRegistry creates a guardrail ProviderHook using the eval registry. Any registered eval handler (including aliases) can be used as a guardrail.
If the handler implements evals.ParamValidator, the params are normalised (ApplyDefaults + NormalizeParams) and passed to ValidateParams before the hook is constructed. This surfaces invalid pack validators at SDK load time instead of silently failing every turn: a rejection is returned wrapped in ErrInvalidGuardrailParams, which the strict compile path (and therefore sdk.Open) treats as fatal.
func ValidatorsToHooks
deprecated
func ValidatorsToHooks(validators []prompt.ValidatorConfig) []hooks.ProviderHook
ValidatorsToHooks is the lenient form: every unusable validator — an unknown eval type or a param set the handler rejects — is logged and skipped, and the usable ones are still returned.
Deprecated: use CompileValidators. This form cannot report either failure, so a typo'd validator is silently dropped and the caller proceeds unprotected. Retained unchanged so existing callers keep their behavior.
func ValidatorsToHooksWithRegistry
deprecated
func ValidatorsToHooksWithRegistry( validators []prompt.ValidatorConfig, registry *evals.EvalTypeRegistry, ) []hooks.ProviderHook
ValidatorsToHooksWithRegistry is the lenient form of CompileValidatorsWithRegistry: every unusable validator — an unknown eval type or a param set the handler rejects — is logged and skipped. A nil registry means the default one.
Deprecated: use CompileValidatorsWithRegistry. This form cannot report either failure, so a typo'd validator is silently dropped and the caller proceeds unprotected.
Types ¶
type GuardrailHookAdapter ¶
type GuardrailHookAdapter struct {
// contains filtered or unexported fields
}
GuardrailHookAdapter wraps an evals.EvalTypeHandler as a hooks.ProviderHook. This bridges the unified eval system to the pipeline's hook infrastructure, allowing any registered eval handler to be used as a guardrail.
Guardrails always enforce: on a hit the adapter mutates the response (truncate or replace) and returns an Enforced decision so the pipeline continues. If you want observe-only behavior, declare an eval — not a guardrail — and assert on it in scenarios.
func (*GuardrailHookAdapter) AfterCall ¶
func (a *GuardrailHookAdapter) AfterCall( ctx context.Context, req *hooks.ProviderRequest, resp *hooks.ProviderResponse, ) hooks.Decision
AfterCall checks provider output when direction is "output" or "both". When the guardrail triggers, it enforces in-place on resp.Message (truncating or replacing content) and returns an Enforced decision.
func (*GuardrailHookAdapter) BeforeCall ¶
func (a *GuardrailHookAdapter) BeforeCall( ctx context.Context, req *hooks.ProviderRequest, ) hooks.Decision
BeforeCall checks input when direction is "input" or "both".
It evaluates only when the last message is a user message (see lastUserTurn). BeforeCall runs once per round inside the tool loop, where later rounds end in a tool-result message rather than user input — evaluating those would score the wrong content and rebill LLM-judged checks every round. The gate is deliberately content-based rather than round-based: a round check would also misfire on a round whose last message is an assistant message, and round numbering is per-ProviderStage (it restarts in each composition sub-pipeline), so it is not a reliable proxy for "there is new user input".
func (*GuardrailHookAdapter) Name ¶
func (a *GuardrailHookAdapter) Name() string
Name returns the eval type identifier for this guardrail.
func (*GuardrailHookAdapter) OnChunk ¶
func (a *GuardrailHookAdapter) OnChunk( ctx context.Context, chunk *providers.StreamChunk, ) hooks.Decision
OnChunk evaluates streaming chunks via StreamableEvalHandler.EvalPartial. When a guardrail triggers, it truncates the chunk content and returns an Enforced decision so the provider stage can stop reading but continue the pipeline.
A chunk is assistant output, so this is the streaming half of AfterCall and gates on direction identically. Without that gate a guardrail declared `direction: input` still scored the model's reply — but only when streaming — and the firing was recorded as an "output" one, since the chunk path stamps that side unconditionally. So an input-only guardrail could block a response it was never meant to judge, under a direction it never declared.
func (*GuardrailHookAdapter) SetEmitter ¶
func (a *GuardrailHookAdapter) SetEmitter(e *events.Emitter)
SetEmitter implements hooks.EmitterAware. The provider stage calls this with the conversation's emitter, which is the only path by which a pack-compiled guardrail becomes observable — WithEmitter covers direct construction.
type GuardrailOption ¶
type GuardrailOption func(*GuardrailHookAdapter)
GuardrailOption configures a GuardrailHookAdapter.
func WithEmitter ¶
func WithEmitter(emitter *events.Emitter) GuardrailOption
WithEmitter gives the guardrail an event emitter so it reports its validation lifecycle — started, and passed when it does not trigger. Firings are emitted by the pipeline stage, which knows the enforcement outcome and also covers func-backed guardrails; see GuardrailHookAdapter.evaluate.
Optional: a guardrail built without an emitter is silent, as before.
func WithJudge ¶ added in v2.5.0
func WithJudge(judge handlers.JudgeProvider) GuardrailOption
WithJudge supplies a DEFAULT judge, used by a judge-backed guardrail whose pack names no provider of its own.
The normal route is the pack: a check names a logical provider from its requires block and the host binds that name, which keeps the host free to change the model behind it. This is for a host driving guardrails with no pack binding in play, and for one that wants a fallback; a named provider always wins over it.
func WithMessage ¶
func WithMessage(msg string) GuardrailOption
WithMessage sets the user-facing message shown when content is blocked.
type Spec ¶
type Spec struct {
// contains filtered or unexported fields
}
Spec is a declared guardrail, not yet built into a hook. Construction errors (unknown eval type, invalid params) surface from Hook() so callers can declare guardrails inline and report all failures at one point — typically sdk.Open.
func Input ¶
func Input(evalType string, params map[string]any, opts ...GuardrailOption) Spec
Input declares an eval-backed guardrail that gates the user's input before the provider call. Any registered eval handler may be named.
guardrails.Input("pii_leakage", nil)
func InputFunc ¶
InputFunc declares a guardrail from a plain function gating user input. The function runs once per user turn: it is skipped on tool-loop rounds, where the last message is a tool result rather than user input.
guardrails.InputFunc("no-wires", func(ctx context.Context, in *hooks.InputRequest) hooks.Decision {
if strings.Contains(in.UserInput, "wire transfer") {
in.Replacement = "I can't help with transfers."
return hooks.Enforced("wire transfer requested", nil)
}
return hooks.Allow
})
func Output ¶
func Output(evalType string, params map[string]any, opts ...GuardrailOption) Spec
Output declares an eval-backed guardrail that gates the assistant's response.
func OutputFunc ¶
OutputFunc declares a guardrail from a plain function gating the assistant response. Mutate OutputRequest.Message in place and return Enforced to rewrite. Returning Enforced also stops the provider round loop and drops any tool calls the response requested; downstream pipeline stages still run.
func (Spec) Hook ¶
func (s Spec) Hook() (hooks.ProviderHook, error)
Hook builds the ProviderHook this Spec describes against the default eval registry. A zero-value Spec returns ErrEmptySpec rather than panicking.
func (Spec) HookWithRegistry ¶
func (s Spec) HookWithRegistry(registry *evals.EvalTypeRegistry) (hooks.ProviderHook, error)
HookWithRegistry builds the ProviderHook resolving an eval-backed guardrail's type against registry. A nil registry means the default one, so HookWithRegistry(nil) and Hook() are equivalent.
Callers that let a user supply their own evals.EvalTypeRegistry — the SDK's WithEvalRegistry — must use this form. Building against the default registry makes a custom eval type unknown, and the guardrail is then dropped rather than enforced (#1717). Func-backed Specs (InputFunc, OutputFunc) ignore the registry: they carry their own logic.