fix(llm): 工具路由配置修改后立即生效,不再硬编码 system prompt

toolrouter 每轮调用都从 DB 读取最新配置,/admin/llm/api 保存后下一条消息即生效,
无需重启。ai.NewService 启动时从 DB 加载初始配置,移除硬编码 prompt 覆盖用户配置的问题。

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-20 00:41:28 +08:00
co-authored by Claude
parent f5dacdce19
commit b9c2b8f0bd
4 changed files with 113 additions and 23 deletions
+7 -5
View File
@@ -60,8 +60,8 @@ func RunAgentToolLoop(ctx context.Context, state *State, profile *llm.Profile, s
if emit != nil {
emit(stream.Frame{Type: "trace", Tool: "agent_tools", Stage: "prepare", Status: "success", Message: "已准备可用工具", Data: map[string]any{"tools": availableNames, "tool_descriptions": toolDescriptions}})
}
if state == nil || state.cfg == nil {
// No tool router config, but we have tools - use primary system prompt
if state == nil {
// No tool router state, but we have tools - use primary system prompt
if strings.TrimSpace(systemPrompt) != "" {
systemMessage := &model.ChatCompletionMessage{
Role: "system",
@@ -74,8 +74,10 @@ func RunAgentToolLoop(ctx context.Context, state *State, profile *llm.Profile, s
}
return finalMessages, nil
}
// 每轮调用都重新加载最新配置,确保管理员在 /admin/llm/api 保存后立即生效
cfg := state.effectiveConfig()
// Use tool router system prompt if available, otherwise fall back to primary system prompt
prompt := strings.TrimSpace(state.cfg.SystemPrompt)
prompt := strings.TrimSpace(cfg.SystemPrompt)
if prompt == "" {
prompt = strings.TrimSpace(systemPrompt)
}
@@ -96,11 +98,11 @@ func RunAgentToolLoop(ctx context.Context, state *State, profile *llm.Profile, s
resp, err := completion.CompleteChat(ctx, routerProfile, model.CreateChatCompletionRequest{
Model: routerProfile.Config.Model,
Messages: decisionMessages,
MaxTokens: &state.cfg.MaxTokens,
MaxTokens: &cfg.MaxTokens,
Tools: definitions,
ToolChoice: model.ToolChoiceStringTypeAuto,
ParallelToolCalls: BoolPtr(false),
}, time.Duration(state.cfg.Timeout)*time.Second)
}, time.Duration(cfg.Timeout)*time.Second)
if err != nil {
return finalMessages, err
}
+40 -6
View File
@@ -17,15 +17,30 @@ type Config struct {
SystemPrompt string
}
// ConfigStore 定义从持久化层读取最新 ToolRouter 配置的能力。
// 每次 RunAgentToolLoop 都会调用 LoadToolRouterConfig,从而保证管理员
// 在 /admin/llm/api 修改配置后立即生效,无需重启。
type ConfigStore interface {
LoadToolRouterConfig() (*Config, error)
}
// State manages the tool router state
type State struct {
cfg *Config
ai *llm.State
cfg *Config
ai *llm.State
store ConfigStore
}
// Option is a function that configures the State
type Option func(*State)
// WithConfigStore 注入运行时配置加载器,State 会在每次需要时拉取最新配置。
func WithConfigStore(store ConfigStore) Option {
return func(s *State) {
s.store = store
}
}
// NewState creates a new tool router state
func NewState(cfg *Config, ai *llm.State, options ...Option) (*State, error) {
if cfg == nil {
@@ -51,12 +66,31 @@ func NewState(cfg *Config, ai *llm.State, options ...Option) (*State, error) {
return state, nil
}
// effectiveConfig 返回当前生效的配置:优先从 store 加载最新值,加载失败时回退到内存 cfg。
// 调用方拿到的永远是非 nil 指针;内存 cfg 也保持同步以便其它读取点。
func (s *State) effectiveConfig() *Config {
if s == nil {
return &Config{}
}
if s.store != nil {
if latest, err := s.store.LoadToolRouterConfig(); err == nil && latest != nil {
s.cfg = latest
return latest
}
}
if s.cfg == nil {
return &Config{}
}
return s.cfg
}
// RouterProfile returns the LLM profile configured for the tool router
func (s *State) RouterProfile(fallback *llm.Profile) *llm.Profile {
if s == nil || s.cfg == nil || s.ai == nil {
if s == nil || s.ai == nil {
return fallback
}
name := strings.TrimSpace(s.cfg.OpenAIName)
cfg := s.effectiveConfig()
name := strings.TrimSpace(cfg.OpenAIName)
if name == "" {
return fallback
}
@@ -69,8 +103,8 @@ func (s *State) RouterProfile(fallback *llm.Profile) *llm.Profile {
// Config returns a copy of the current configuration
func (s *State) Config() Config {
if s == nil || s.cfg == nil {
if s == nil {
return Config{}
}
return *s.cfg
return *s.effectiveConfig()
}