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,6 +11,7 @@ import (
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/auth"
|
||||
capreg "git.yeij.top/AskaEth/Cyrene_v2/internal/capability/registry"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/config/runtimeconfig"
|
||||
apperr "git.yeij.top/AskaEth/Cyrene_v2/internal/errors"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/knowledge"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk"
|
||||
@@ -47,6 +48,7 @@ type policyRequest struct {
|
||||
type AdminLlmProvider struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Model string `json:"model"`
|
||||
Multimodal bool `json:"multimodal"`
|
||||
@@ -164,39 +166,49 @@ func (s *Server) adminListIoT(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// adminGetLlm returns the LLM provider/model configuration read from the live
|
||||
// config. API keys are never returned (the wire shape carries no key field), so
|
||||
// the handler leaks nothing sensitive.
|
||||
// provider stack (models.json, falling back to .env). API keys are never
|
||||
// returned — the wire shape carries no key field — so the handler leaks nothing
|
||||
// sensitive.
|
||||
func (s *Server) adminGetLlm(w http.ResponseWriter, r *http.Request) {
|
||||
cfg := s.cfg.LLM
|
||||
providers := []AdminLlmProvider{
|
||||
{
|
||||
ID: "openai",
|
||||
Name: "OpenAI",
|
||||
BaseURL: cfg.BaseURL,
|
||||
Model: cfg.Model,
|
||||
Multimodal: cfg.VisionModel != "",
|
||||
Enabled: cfg.Provider != "anthropic",
|
||||
Fallback: cfg.Fallback,
|
||||
},
|
||||
{
|
||||
ID: "anthropic",
|
||||
Name: "Anthropic",
|
||||
BaseURL: cfg.AnthropicBaseURL,
|
||||
Model: cfg.AnthropicModel,
|
||||
Multimodal: false,
|
||||
Enabled: cfg.Provider == "anthropic",
|
||||
Fallback: cfg.AnthropicFallback,
|
||||
},
|
||||
groups := s.currentLlmGroups()
|
||||
providers := make([]AdminLlmProvider, 0, len(groups))
|
||||
for i, g := range groups {
|
||||
providers = append(providers, AdminLlmProvider{
|
||||
ID: g.ID,
|
||||
Name: providerDisplayName(g),
|
||||
Provider: g.Provider,
|
||||
BaseURL: g.BaseURL,
|
||||
Model: g.Model,
|
||||
Multimodal: g.Multimodal,
|
||||
Enabled: i == 0,
|
||||
Fallback: g.Fallback,
|
||||
})
|
||||
}
|
||||
// Fold in providers added at runtime via the admin "add provider" write.
|
||||
// They are process-lifetime and not bound to the model router, so the panel
|
||||
// reflects them but the response marks nothing else.
|
||||
s.llmMu.RLock()
|
||||
providers = append(providers, s.llmExtra...)
|
||||
s.llmMu.RUnlock()
|
||||
writeJSON(w, http.StatusOK, AdminLlmConfig{Providers: providers})
|
||||
}
|
||||
|
||||
// currentLlmGroups returns the provider groups to display: the live stack's
|
||||
// groups when one is wired, otherwise the env-synthesized single group.
|
||||
func (s *Server) currentLlmGroups() []runtimeconfig.Group {
|
||||
if s.llm != nil {
|
||||
return s.llm.Groups()
|
||||
}
|
||||
return runtimeconfig.GroupFromEnv(s.cfg.LLM)
|
||||
}
|
||||
|
||||
// providerDisplayName returns a friendly label for a provider group, using the
|
||||
// vendor name for the two known protocols and the group id otherwise.
|
||||
func providerDisplayName(g runtimeconfig.Group) string {
|
||||
switch g.Provider {
|
||||
case runtimeconfig.ProviderOpenAI:
|
||||
return "OpenAI"
|
||||
case runtimeconfig.ProviderAnthropic:
|
||||
return "Anthropic"
|
||||
default:
|
||||
return g.ID
|
||||
}
|
||||
}
|
||||
|
||||
// adminListAutomations lists automations and reminders (admin read).
|
||||
func (s *Server) adminListAutomations(w http.ResponseWriter, r *http.Request) {
|
||||
out := make([]AdminAutomationRecord, 0)
|
||||
|
||||
Reference in new issue
Block a user