Files
Cyrene_v2/internal/runtime/agent/options.go
T

97 lines
3.1 KiB
Go

package agent
import (
"fmt"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
)
// Option bounds and defaults. These are compile-time constants so no magic
// number appears in the runtime path.
const (
defaultMaxRounds = 8
minTemperature = 0.0
maxTemperature = 2.0
)
// Options carries the config knobs for an Agent. Zero values fall back to a
// provider default: temperature uses 0 to mean "provider default", and
// MaxRounds/MaxTokens use 0 to mean "default"/"provider default" respectively.
type Options struct {
// Model is the requested model name. Empty lets the injected Router or the
// adapter's own default decide.
Model string
// Temperature is the sampling temperature. A nonzero value is forwarded to
// the model; zero means the provider default.
Temperature float64
// MaxRounds is the tool-loop upper bound. Zero is defaulted to 8.
MaxRounds int
// Purpose labels every CallRecord emitted for accounting ("chat", "agent").
Purpose string
// MaxTokens caps generated tokens. Zero means the provider default.
MaxTokens int
}
// Option configures an Agent at construction time. Options are applied in the
// order they are passed to New.
type Option func(*Agent)
// WithModel injects the LLM backend used by RunStep: a model.Adapter plus an
// optional per-role Router. When router is non-nil it overrides the Request's
// Model from the request contents — which is how vision requests are routed to
// the vision model — otherwise the agent's configured Model or the adapter
// default applies.
func WithModel(adapter model.Adapter, router *model.Router) Option {
return func(a *Agent) {
a.adapter = adapter
a.router = router
}
}
// WithOptions applies opts to the agent's config, merging over the defaults. A
// zero Model, MaxRounds, Purpose or MaxTokens leaves the existing value
// unchanged; Temperature is copied verbatim.
func WithOptions(opts Options) Option {
return func(a *Agent) {
if opts.Model != "" {
a.opts.Model = opts.Model
}
a.opts.Temperature = opts.Temperature
if opts.MaxRounds != 0 {
a.opts.MaxRounds = opts.MaxRounds
}
if opts.Purpose != "" {
a.opts.Purpose = opts.Purpose
}
if opts.MaxTokens != 0 {
a.opts.MaxTokens = opts.MaxTokens
}
}
}
// WithRecorder wires a CallRecord sink. After each successful model call the
// agent emits one CallRecord to the recorder.
func WithRecorder(r model.Recorder) Option {
return func(a *Agent) { a.recorder = r }
}
// WithHandoff sets the agent's handoff-target placeholder (M3 orchestration).
func WithHandoff(h *Handoff) Option {
return func(a *Agent) { a.handoff = h }
}
// validateOptions rejects out-of-range option values so bad config fails at
// construction rather than at request time.
func validateOptions(o Options) error {
if o.Temperature < minTemperature || o.Temperature > maxTemperature {
return fmt.Errorf("%w: temperature %v out of range [%v, %v]", ErrBadOptions, o.Temperature, minTemperature, maxTemperature)
}
if o.MaxRounds < 0 {
return fmt.Errorf("%w: max rounds %d", ErrBadOptions, o.MaxRounds)
}
if o.MaxTokens < 0 {
return fmt.Errorf("%w: max tokens %d", ErrBadOptions, o.MaxTokens)
}
return nil
}