Files
Agent-System-Backend/internal/settings/settings.go
T
weichen 0d74ec94dd Implement the backend: store/bus/runmgr core
Details (original message):
Implement the backend: store/bus/runmgr core, mock and LLM runners (agent-native client), HITL three-branch routing, subagent runs, builtin and declarative HTTP tools, ingest parsers (md/txt/docx/pdf-text), knowledge fulltext, layered settings, profiles, plugin host, REST+SSE API, single-binary entrypoint; e2e and unit tests.
2026-09-29 17:53:24 +00:00

122 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package settings 实现分层设置(4.9):system 默认 → user → project → conversation,
// schema 驱动设置页(生效时机 hot / new-run / restart 显式标注)。
package settings
import (
core "agent-system-core"
"agent-system-backend/internal/store"
)
// Scope 层级键:user 与 project:<id> 落库;conversation 经会话画像选择实现。
const (
ScopeUser = "user"
ScopeProjectPrefix = "project:"
)
// SystemDefaults 是系统级默认(代码内置,settings.system.json 语义)。
func SystemDefaults() map[string]any {
return map[string]any{
"models": map[string]any{
"chat": map[string]any{
"baseUrl": "", // 例:https://api.openai.com/v1 (OpenAI 兼容)
"apiKey": "",
"model": "",
},
"lightweight": map[string]any{"baseUrl": "", "apiKey": "", "model": ""},
"embedding": map[string]any{"baseUrl": "", "apiKey": "", "model": ""},
"rerank": map[string]any{"baseUrl": "", "apiKey": "", "model": ""},
},
"runtime": map[string]any{
"mock": false, // true = 全部会话走 mock runner(演示/测试)
},
"context": map[string]any{
"maxHistoryMessages": 40,
"maxToolResultChars": 20000,
},
"retrieval": map[string]any{"topK": 5},
"ui": map[string]any{"theme": "dark"},
"plugins": map[string]any{"dir": "./plugins"},
}
}
// Schema 返回设置描述清单(设置页渲染依据;插件设置项由 pluginhost 追加)。
func Schema() []core.SettingDesc {
return []core.SettingDesc{
{Key: "models.chat.baseUrl", Group: "models", Label: "主模型 Base URL", Type: core.SettingString, Default: "", Effect: core.EffectHot, Description: "OpenAI 兼容端点,如 https://api.openai.com/v1"},
{Key: "models.chat.apiKey", Group: "models", Label: "主模型 API Key", Type: core.SettingSecret, Default: "", Effect: core.EffectHot},
{Key: "models.chat.model", Group: "models", Label: "主模型名称", Type: core.SettingString, Default: "", Effect: core.EffectNewRun},
{Key: "models.lightweight.baseUrl", Group: "models", Label: "轻量模型 Base URL", Type: core.SettingString, Default: "", Effect: core.EffectHot, Description: "标题生成/澄清等轻任务;空则回落主模型"},
{Key: "models.lightweight.apiKey", Group: "models", Label: "轻量模型 API Key", Type: core.SettingSecret, Default: "", Effect: core.EffectHot},
{Key: "models.lightweight.model", Group: "models", Label: "轻量模型名称", Type: core.SettingString, Default: "", Effect: core.EffectNewRun},
{Key: "models.embedding.baseUrl", Group: "models", Label: "Embedding Base URL", Type: core.SettingString, Default: "", Effect: core.EffectHot, Description: "RAG/记忆检索用(向量管线远期启用)"},
{Key: "models.embedding.apiKey", Group: "models", Label: "Embedding API Key", Type: core.SettingSecret, Default: "", Effect: core.EffectHot},
{Key: "models.embedding.model", Group: "models", Label: "Embedding 模型", Type: core.SettingString, Default: "", Effect: core.EffectHot},
{Key: "models.rerank.baseUrl", Group: "models", Label: "Reranker Base URL", Type: core.SettingString, Default: "", Effect: core.EffectHot},
{Key: "models.rerank.apiKey", Group: "models", Label: "Reranker API Key", Type: core.SettingSecret, Default: "", Effect: core.EffectHot},
{Key: "models.rerank.model", Group: "models", Label: "Reranker 模型", Type: core.SettingString, Default: "", Effect: core.EffectHot},
{Key: "runtime.mock", Group: "runtime", Label: "演示模式(mock runner)", Type: core.SettingBool, Default: false, Effect: core.EffectNewRun, Description: "不调用真实模型,按脚本回放事件流"},
{Key: "context.maxHistoryMessages", Group: "context", Label: "历史窗口条数", Type: core.SettingNumber, Default: 40, Effect: core.EffectNewRun},
{Key: "context.maxToolResultChars", Group: "context", Label: "工具结果上限(字符)", Type: core.SettingNumber, Default: 20000, Effect: core.EffectNewRun},
{Key: "retrieval.topK", Group: "retrieval", Label: "检索命中数", Type: core.SettingNumber, Default: 5, Effect: core.EffectHot},
{Key: "ui.theme", Group: "ui", Label: "主题", Type: core.SettingEnum, Default: "dark", Options: []string{"dark", "light"}, Effect: core.EffectHot},
{Key: "plugins.dir", Group: "plugins", Label: "插件目录", Type: core.SettingString, Default: "./plugins", Effect: core.EffectRestart},
}
}
// Service 提供有效设置计算与分层写入。
type Service struct{ st *store.Store }
func NewService(st *store.Store) *Service { return &Service{st: st} }
// Effective 计算覆盖链结果:system → user → project(conversation 层经画像实现)。
func (s *Service) Effective(projectID string) core.EffectiveSettings {
eff := core.MergeSettings(SystemDefaults(), map[string]any{})
if user, err := s.st.GetSettingsScope(ScopeUser); err == nil {
eff = core.MergeSettings(eff, user)
}
if projectID != "" {
if proj, err := s.st.GetSettingsScope(ScopeProjectPrefix + projectID); err == nil {
eff = core.MergeSettings(eff, proj)
}
}
return eff
}
func (s *Service) UserScope() (map[string]any, error) { return s.st.GetSettingsScope(ScopeUser) }
func (s *Service) ProjectScope(projectID string) (map[string]any, error) {
return s.st.GetSettingsScope(ScopeProjectPrefix + projectID)
}
func (s *Service) MergeUser(values map[string]any) error { return s.st.MergeSettingsScope(ScopeUser, values) }
func (s *Service) MergeProject(projectID string, values map[string]any) error {
return s.st.MergeSettingsScope(ScopeProjectPrefix+projectID, values)
}
// Masked 返回脱敏视图(secret 类 key 掩码),用于 API 响应。Effective 每次
// 深合并产出新 map,就地掩码不影响后续计算。
func Masked(eff core.EffectiveSettings) map[string]any {
out := map[string]any(eff)
maskSecrets(out)
return out
}
func maskSecrets(m map[string]any) {
for k, v := range m {
if vm, ok := v.(map[string]any); ok {
maskSecrets(vm)
continue
}
if isSecretKey(k) {
if s, ok := v.(string); ok && s != "" {
m[k] = "••••••"
}
}
}
}
func isSecretKey(k string) bool {
return k == "apiKey" || k == "api_key" || k == "secret" || k == "token"
}