- 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(后续纳入); 保存组=生效组(第一版语义)
99 lines
3.3 KiB
Go
99 lines
3.3 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. router is a model.ModelRouter (implemented by *model.Router
|
|
// and by the hot-swappable model.RuntimeLLM) so a reloaded provider can swap it
|
|
// without rebuilding the agent.
|
|
func WithModel(adapter model.Adapter, router model.ModelRouter) 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
|
|
}
|