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(后续纳入); 保存组=生效组(第一版语义)
This commit is contained in:
1 parent
8cde0162e6
commit
2d7151f979
16 files changed
+1079
-108
No files matched your search
+11
-11
@@ -18,6 +18,7 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/session"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/config"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/config/runtimeconfig"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/db"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/iot"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||||
@@ -77,6 +78,7 @@ type app struct {
|
||||
db *sql.DB
|
||||
bus bus.Bus
|
||||
modelAdapter model.Adapter
|
||||
llm *model.RuntimeLLM
|
||||
personas *personareg.Registry
|
||||
caps *registry.Registry
|
||||
dispatcher *tool.Dispatcher
|
||||
@@ -259,16 +261,13 @@ func (a *app) wireMemory(ctx context.Context) error {
|
||||
// wireRuntime builds the model adapter, tool dispatcher, guardrails, the agent
|
||||
// and its loop, tracing, persistence and the orchestration scheduler.
|
||||
func (a *app) wireRuntime() error {
|
||||
adapter, aerr := model.NewChatChain(a.cfg.LLM)
|
||||
if aerr != nil {
|
||||
a.log.Warn("llm adapter unavailable", "error", aerr)
|
||||
adapter = nil
|
||||
}
|
||||
// Keep the model adapter for cross-function use (e.g. the channel-bridge
|
||||
// review model that prunes low-value messages, design/12 §6 策略四).
|
||||
a.modelAdapter = adapter
|
||||
router := model.NewRouter(model.DefaultModelForProvider(a.cfg.LLM),
|
||||
model.WithVisionModel(a.cfg.LLM.VisionModel))
|
||||
// The LLM provider stack is hot-swappable: it loads models.json (or falls
|
||||
// back to .env) and the panel save endpoint reloads it without a restart
|
||||
// (design/04 §3.1). a.modelAdapter stays an Adapter (the runtime stack
|
||||
// implements it) for the channel-bridge review model (design/12 §6 策略四).
|
||||
runtimeStack := model.NewRuntimeLLM(runtimeconfig.DefaultPath(resolveDataDir(a.cfg)), a.cfg.LLM, a.log)
|
||||
a.modelAdapter = runtimeStack
|
||||
a.llm = runtimeStack
|
||||
|
||||
dispatcher, derr := tool.New(a.caps, tool.WithDefaultTimeout(defaultToolTimeout))
|
||||
if derr != nil {
|
||||
@@ -293,7 +292,7 @@ func (a *app) wireRuntime() error {
|
||||
guards := guardrail.New()
|
||||
instructions := a.defaultInstructions()
|
||||
ag, aerr := agent.New(instructions, a.dispatcher, guards,
|
||||
agent.WithModel(adapter, router),
|
||||
agent.WithModel(runtimeStack, runtimeStack),
|
||||
agent.WithOptions(agent.Options{MaxRounds: defaultMaxRounds, Purpose: "chat"}))
|
||||
if aerr != nil {
|
||||
return fmt.Errorf("agent: %w", aerr)
|
||||
@@ -549,6 +548,7 @@ func (a *app) wireFrontend() error {
|
||||
Users: apiKeys,
|
||||
Backthink: a.backthink,
|
||||
RAG: ragStore,
|
||||
LLM: a.llm,
|
||||
AuditStore: auditStore,
|
||||
Streaming: true,
|
||||
Frontend: resolveFrontendFS(a.log),
|
||||
|
||||
Reference in new issue
Block a user