只展示代码,不修改文件。以下是方案 A(刷新钩子)的完整写法。
internal/config/sections.go
// onSectionApplied 是配置段成功落盘并写入内存配置后的回调(可为 nil)。 // 由调用方(通常是 container 层)在启动时注入,用于把配置变更同步到 // 那些在启动时就已生成快照的运行时组件(例如 agent 的模型提供商表)。 // // 放在 config 包内而不是直接 import agent 包,是为了避免 config ← agent 的循环依赖。 var onSectionApplied func(key string) // SetSectionAppliedHook 注册「配置段已生效」回调;传 nil 表示取消。 // 必须在服务开始处理请求之前调用。 func SetSectionAppliedHook(fn func(key string)) { onSectionApplied = fn } // notifySectionApplied 通知回调;回调内的 panic 不应影响保存结果。 func notifySectionApplied(key string) { if onSectionApplied == nil { return } defer func() { if r := recover(); r != nil { logger.Errorf(context.Background(), "[Config] section applied hook panic for %q: %v", key, r) } }() onSectionApplied(key) }
在 Section.Apply 调用处(internal/handler/config.go:126)触发:
Section.Apply
internal/handler/config.go:126
if err := section.Apply(h.cfg, value); err != nil { c.Error(errors.NewInternalServerError("failed to apply config section").WithDetails(err.Error())) return } // 配置已落盘并写入内存,通知运行时组件刷新快照(未注册回调时为空操作)。 config.NotifySectionApplied(section.Key)
(用导出名 NotifySectionApplied,因为 handler 在别的包。也可以把触发点收进 config 包的一个 ApplySection(cfg, key, value) 包装函数里,保持 handler 无感知。)
NotifySectionApplied
config
ApplySection(cfg, key, value)
internal/agent/client.go
// SetProviders 更新默认 Options 中的模型提供商配置表快照(热更新,无需重启)。 func (c *Client) SetProviders(providers map[string]ModelProviderConfig) { c.mu.Lock() c.defaultOptions.Providers = providers c.mu.Unlock() } // SetOptions 整体替换默认 Options 中由配置派生的部分(目前即 Providers)。 // 供 container 层在配置变更后调用;其余进程级服务能力(Tools / FileReader 等)不受影响。 func (c *Client) SetProvidersFrom(fn func() map[string]ModelProviderConfig) { c.SetProviders(fn()) }
internal/container/container.go
// registerConfigHooks 把「配置段已生效」回调注入 config 包。 // // config 包不认识 agent.Client,这里通过闭包把两者连起来: // agent 段保存后,client 立刻重建 providers 快照,下一次请求即使用新配置。 func registerConfigHooks(cfg *config.Config, client *agent.Client) { config.SetSectionAppliedHook(func(key string) { switch key { case "agent": client.SetProviders(agentProviders(cfg)) } }) }
在 DI 容器里、*agent.Client 构造完成之后调用(container.go 的 Provide 区块附近):
*agent.Client
must(container.Invoke(func(cfg *config.Config, client *agent.Client) { registerConfigHooks(cfg, client) }))
Apply
SetSectionAppliedHook
SetProviders
options()
Options.ProviderSource
func()
defaultOptions
你已经实现了 B,A 属于"推送式"替代方案,两者也可以共存(ProviderSource 优先时会覆盖 SetProviders 的结果,见 client.go:options() 的写法)。
ProviderSource
client.go:options()
Github开源生信云平台 DEMO