97 lines
3.1 KiB
Go
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
|
|
}
|