Files
Aska 2d7151f979
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(config): 运行时配置体系①——LLM 多厂商独立组落 models.json + 面板落盘 + 热更新
- 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(后续纳入); 保存组=生效组(第一版语义)
2026-09-07 13:07:58 +08:00

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
}