Files
Cyrene_v2/internal/runtime/agent/agent.go
T
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

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 }