- runtimeconfig/models.go: models.json 承载 [{id,provider,baseUrl,apiKey,model,fallback?,multimodal?}] 多厂商独立组(首项=生效)
- LoadModels 优先文件/回退env(GroupFromEnv), SaveModels(0600+tmp原子写), Activate/Upsert/MaskedKey脱敏
- 面板 POST /admin/llm 落盘 models.json(取代temporary内存) + GET 列组(脱敏)
- runtime/model/runtime.go: RuntimeLLM(adapter+router) atomic快照可热替换, Reload 重建不中断进行中请求; per-role路由用接口
- wire 用 NewRuntimeLLM(adapter+router), 保留 a.modelAdapter(=runtime stack, 审查模型随热更新)
- .env 退兜底(.env.example注明); apiKey不打印/Config()脱敏/热更新不中断
- 全仓 go build/vet/test 绿(54包)+交叉编译+前端build绿(79模块); api-client v0.15已同步
- 遗留: vision ModelInfo 仍读静态env(后续纳入); 保存组=生效组(第一版语义)
112 lines
4.5 KiB
Go
112 lines
4.5 KiB
Go
// 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 }
|