mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-03 07:12:50 +00:00
refactor(media): migrate builtin tool settings from legacy flat to chain format
- Update seed defaults to use chain format {"providers":[...]}
- Add startup auto-migration for existing legacy flat settings in DB
- Remove legacy flat format parsing from parseChainSettings()
This commit is contained in:
1 parent
fb309c3c2b
commit
b9e9e6e34a
3 files changed
+96
-45
No files matched your search
@@ -658,6 +658,7 @@ func runGateway() {
|
||||
// Seed + apply builtin tool disables
|
||||
if pgStores.BuiltinTools != nil {
|
||||
seedBuiltinTools(context.Background(), pgStores.BuiltinTools)
|
||||
migrateBuiltinToolSettings(context.Background(), pgStores.BuiltinTools)
|
||||
applyBuiltinToolDisables(context.Background(), pgStores.BuiltinTools, toolsReg)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,33 +42,33 @@ func builtinToolSeedData() []store.BuiltinToolDef {
|
||||
Requires: []string{"knowledge_graph"},
|
||||
},
|
||||
|
||||
// media
|
||||
// media — seed uses chain format: {"providers":[...]}
|
||||
{Name: "read_image", DisplayName: "Read Image", Description: "Analyze images using a vision-capable LLM provider", Category: "media", Enabled: true,
|
||||
Settings: json.RawMessage(`{"provider":"openrouter","model":"google/gemini-2.5-flash-image"}`),
|
||||
Settings: json.RawMessage(`{"providers":[{"provider":"openrouter","model":"google/gemini-2.5-flash-image","enabled":true,"timeout":120,"max_retries":2}]}`),
|
||||
Requires: []string{"vision_provider"},
|
||||
},
|
||||
{Name: "read_document", DisplayName: "Read Document", Description: "Analyze documents (PDF, Word, Excel, PowerPoint, CSV, etc.) using a document-capable LLM provider", Category: "media", Enabled: true,
|
||||
Settings: json.RawMessage(`{"provider":"gemini","model":"gemini-2.5-flash"}`),
|
||||
Settings: json.RawMessage(`{"providers":[{"provider":"gemini","model":"gemini-2.5-flash","enabled":true,"timeout":120,"max_retries":2}]}`),
|
||||
Requires: []string{"document_provider"},
|
||||
},
|
||||
{Name: "create_image", DisplayName: "Create Image", Description: "Generate images from text prompts using an image generation provider", Category: "media", Enabled: true,
|
||||
Settings: json.RawMessage(`{"provider":"openrouter","model":"google/gemini-2.5-flash-image"}`),
|
||||
Settings: json.RawMessage(`{"providers":[{"provider":"openrouter","model":"google/gemini-2.5-flash-image","enabled":true,"timeout":120,"max_retries":2}]}`),
|
||||
Requires: []string{"image_gen_provider"},
|
||||
},
|
||||
{Name: "read_audio", DisplayName: "Read Audio", Description: "Analyze audio files (speech, music, sounds) using an audio-capable LLM provider", Category: "media", Enabled: true,
|
||||
Settings: json.RawMessage(`{"provider":"gemini","model":"gemini-2.5-flash"}`),
|
||||
Settings: json.RawMessage(`{"providers":[{"provider":"gemini","model":"gemini-2.5-flash","enabled":true,"timeout":120,"max_retries":2}]}`),
|
||||
Requires: []string{"audio_provider"},
|
||||
},
|
||||
{Name: "read_video", DisplayName: "Read Video", Description: "Analyze video files using a video-capable LLM provider", Category: "media", Enabled: true,
|
||||
Settings: json.RawMessage(`{"provider":"gemini","model":"gemini-2.5-flash"}`),
|
||||
Settings: json.RawMessage(`{"providers":[{"provider":"gemini","model":"gemini-2.5-flash","enabled":true,"timeout":120,"max_retries":2}]}`),
|
||||
Requires: []string{"video_provider"},
|
||||
},
|
||||
{Name: "create_video", DisplayName: "Create Video", Description: "Generate videos from text descriptions using AI", Category: "media", Enabled: true,
|
||||
Settings: json.RawMessage(`{"provider":"gemini","model":"veo-3.0-generate-preview"}`),
|
||||
Settings: json.RawMessage(`{"providers":[{"provider":"gemini","model":"veo-3.0-generate-preview","enabled":true,"timeout":120,"max_retries":2}]}`),
|
||||
Requires: []string{"video_gen_provider"},
|
||||
},
|
||||
{Name: "create_audio", DisplayName: "Create Audio", Description: "Generate music or sound effects from text descriptions using AI", Category: "media", Enabled: true,
|
||||
Settings: json.RawMessage(`{"provider":"minimax","model":"music-2.5+"}`),
|
||||
Settings: json.RawMessage(`{"providers":[{"provider":"minimax","model":"music-2.5+","enabled":true,"timeout":120,"max_retries":2}]}`),
|
||||
Requires: []string{"audio_gen_provider"},
|
||||
},
|
||||
{Name: "tts", DisplayName: "Text to Speech", Description: "Convert text to natural-sounding speech audio", Category: "media", Enabled: true,
|
||||
@@ -137,6 +137,78 @@ func seedBuiltinTools(ctx context.Context, bts store.BuiltinToolStore) {
|
||||
slog.Info("builtin tools seeded", "count", len(seeds))
|
||||
}
|
||||
|
||||
// mediaToolNames lists media tools whose settings should use chain format.
|
||||
var mediaToolNames = map[string]bool{
|
||||
"read_image": true, "read_document": true, "create_image": true,
|
||||
"read_audio": true, "read_video": true, "create_video": true, "create_audio": true,
|
||||
}
|
||||
|
||||
// migrateBuiltinToolSettings converts legacy flat settings {"provider":"X","model":"Y"}
|
||||
// to chain format {"providers":[...]} in the database. Runs once at startup.
|
||||
func migrateBuiltinToolSettings(ctx context.Context, bts store.BuiltinToolStore) {
|
||||
all, err := bts.List(ctx)
|
||||
if err != nil {
|
||||
slog.Warn("builtin_tools: failed to list for migration", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
var migrated int
|
||||
for _, t := range all {
|
||||
if !mediaToolNames[t.Name] {
|
||||
continue
|
||||
}
|
||||
if len(t.Settings) == 0 || string(t.Settings) == "{}" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Detect legacy flat format: has "provider" key but no "providers" key
|
||||
var raw map[string]json.RawMessage
|
||||
if err := json.Unmarshal(t.Settings, &raw); err != nil {
|
||||
continue
|
||||
}
|
||||
if _, hasProviders := raw["providers"]; hasProviders {
|
||||
continue // already chain format
|
||||
}
|
||||
if _, hasProvider := raw["provider"]; !hasProvider {
|
||||
continue // neither format, skip
|
||||
}
|
||||
|
||||
// Parse legacy flat fields
|
||||
var flat struct {
|
||||
Provider string `json:"provider"`
|
||||
Model string `json:"model"`
|
||||
}
|
||||
if err := json.Unmarshal(t.Settings, &flat); err != nil || flat.Provider == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert to chain format
|
||||
chain := map[string]any{
|
||||
"providers": []map[string]any{{
|
||||
"provider": flat.Provider,
|
||||
"model": flat.Model,
|
||||
"enabled": true,
|
||||
"timeout": 120,
|
||||
"max_retries": 2,
|
||||
}},
|
||||
}
|
||||
newSettings, err := json.Marshal(chain)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := bts.Update(ctx, t.Name, map[string]any{"settings": json.RawMessage(newSettings)}); err != nil {
|
||||
slog.Warn("builtin_tools: failed to migrate settings", "tool", t.Name, "error", err)
|
||||
continue
|
||||
}
|
||||
migrated++
|
||||
}
|
||||
|
||||
if migrated > 0 {
|
||||
slog.Info("builtin_tools: migrated legacy settings to chain format", "count", migrated)
|
||||
}
|
||||
}
|
||||
|
||||
// applyBuiltinToolDisables unregisters disabled builtin tools from the registry.
|
||||
// Called at startup and on cache invalidation.
|
||||
func applyBuiltinToolDisables(ctx context.Context, bts store.BuiltinToolStore, toolsReg *tools.Registry) {
|
||||
|
||||
@@ -25,12 +25,10 @@ type MediaProviderEntry struct {
|
||||
}
|
||||
|
||||
// mediaProviderChain is the settings JSON structure for media tools.
|
||||
// Supports both new (providers array) and legacy (flat provider/model) formats.
|
||||
// Only supports chain format: {"providers":[...]}.
|
||||
// Legacy flat format is auto-migrated at startup (see cmd/gateway_builtin_tools.go).
|
||||
type mediaProviderChain struct {
|
||||
Providers []MediaProviderEntry `json:"providers,omitempty"`
|
||||
// Legacy fields (backward compat)
|
||||
Provider string `json:"provider,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
}
|
||||
|
||||
// applyDefaults fills in zero-value fields with sensible defaults.
|
||||
@@ -97,41 +95,21 @@ func parseChainSettings(raw []byte, defaultModels map[string]string) []MediaProv
|
||||
return nil
|
||||
}
|
||||
|
||||
// New format: providers array
|
||||
if len(chain.Providers) > 0 {
|
||||
var result []MediaProviderEntry
|
||||
for _, e := range chain.Providers {
|
||||
if !e.Enabled {
|
||||
continue
|
||||
}
|
||||
if e.Provider == "" {
|
||||
continue
|
||||
}
|
||||
if e.Model == "" {
|
||||
e.Model = defaultModels[e.Provider]
|
||||
}
|
||||
e.applyDefaults()
|
||||
result = append(result, e)
|
||||
var result []MediaProviderEntry
|
||||
for _, e := range chain.Providers {
|
||||
if !e.Enabled {
|
||||
continue
|
||||
}
|
||||
return result
|
||||
if e.Provider == "" {
|
||||
continue
|
||||
}
|
||||
if e.Model == "" {
|
||||
e.Model = defaultModels[e.Provider]
|
||||
}
|
||||
e.applyDefaults()
|
||||
result = append(result, e)
|
||||
}
|
||||
|
||||
// Legacy format: flat provider/model
|
||||
if chain.Provider != "" {
|
||||
model := chain.Model
|
||||
if model == "" {
|
||||
model = defaultModels[chain.Provider]
|
||||
}
|
||||
entry := MediaProviderEntry{
|
||||
Provider: chain.Provider,
|
||||
Model: model,
|
||||
Enabled: true,
|
||||
}
|
||||
entry.applyDefaults()
|
||||
return []MediaProviderEntry{entry}
|
||||
}
|
||||
|
||||
return nil
|
||||
return result
|
||||
}
|
||||
|
||||
// buildDefaultChain creates a chain from the hardcoded priority list,
|
||||
|
||||
Reference in new issue
Block a user