// Package agent implements the Agent entity of the Cyrene v2 runtime // (DevDocs/design/04-运行时.md §1). An Agent is an instruction string plus an // optional set of tools, an optional guardrail pipeline and an optional // handoff, wired to a model Adapter (and an optional per-role Router). It // executes a single step of the agent loop — assemble a request, run input and // output guardrails, call the model and decode any tool calls — without owning // the loop itself, which lives in internal/runtime/loop (M2). package agent import ( "errors" "fmt" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/guardrail" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/tool" ) // Sentinel errors returned by the Agent. Use errors.Is to test for them; they // are returned directly or wrapped with %w so the underlying cause stays // available. var ( // ErrEmptyInstructions is returned by New when instructions is empty. ErrEmptyInstructions = errors.New("agent: empty instructions") // ErrBadOptions is returned by New when an option value is out of range. ErrBadOptions = errors.New("agent: invalid options") // ErrNoAdapter is returned by RunStep when no model Adapter was injected. ErrNoAdapter = errors.New("agent: no model adapter") ) // Agent is a single, self-contained runtime entity: an instruction string, an // optional tool Dispatcher, an optional guardrail Pipeline, the model backend // and an optional handoff placeholder. It is safe for concurrent use as long as // its fields are not mutated after construction; RunStep builds a local Request // and delegates to the (concurrency-safe) adapter, dispatcher and guardrail. type Agent struct { instructions string tools *tool.Dispatcher guards *guardrail.Pipeline adapter model.Adapter router model.ModelRouter recorder model.Recorder handoff *Handoff opts Options } // New builds an Agent from an instruction string, an optional tool Dispatcher, // an optional guardrail Pipeline and zero or more options applied in order over // the defaults (default MaxRounds is 8). It returns ErrEmptyInstructions when // instructions is blank and ErrBadOptions when an option value is out of range. // // The concrete adapter/router used by RunStep are injected through the options // (see WithModel); tools and guardrails may be nil to run a bare step, and the // agent still works as long as an adapter is supplied before RunStep. func New(instructions string, tools *tool.Dispatcher, guards *guardrail.Pipeline, opts ...Option) (*Agent, error) { a := &Agent{ instructions: instructions, tools: tools, guards: guards, opts: Options{MaxRounds: defaultMaxRounds}, } if a.instructions == "" { return nil, ErrEmptyInstructions } for _, o := range opts { if o != nil { o(a) } } if err := validateOptions(a.opts); err != nil { return nil, fmt.Errorf("agent: new: %w", err) } return a, nil } // Instructions returns the agent's instruction/system prompt. func (a *Agent) Instructions() string { return a.instructions } // Tools returns the agent's tool dispatcher, or nil when none was configured. func (a *Agent) Tools() *tool.Dispatcher { return a.tools } // Guardrails returns the agent's guardrail pipeline, or nil when none was // configured. func (a *Agent) Guardrails() *guardrail.Pipeline { return a.guards } // Adapter returns the injected model adapter. func (a *Agent) Adapter() model.Adapter { return a.adapter } // Router returns the injected per-role router, or nil. The concrete implementation // may be a *model.Router or the hot-swappable model.RuntimeLLM. func (a *Agent) Router() model.ModelRouter { return a.router } // Model returns the configured model name. Empty means the injected Router or // the adapter's own default decides. func (a *Agent) Model() string { return a.opts.Model } // Temperature returns the configured sampling temperature. func (a *Agent) Temperature() float64 { return a.opts.Temperature } // MaxRounds returns the tool-loop upper bound consumed by the loop (M2, // external to this package). func (a *Agent) MaxRounds() int { return a.opts.MaxRounds } // Purpose returns the call-purpose label recorded on each CallRecord. func (a *Agent) Purpose() string { return a.purpose() } // MaxTokens returns the token cap passed to the model. func (a *Agent) MaxTokens() int { return a.opts.MaxTokens } // Options returns a copy of the agent's current config options. func (a *Agent) Options() Options { return a.opts }