Skip to content

Commit b171610

Browse files
committed
fix(gallery): Use YAML v3 to avoid merging maps with incompatible keys
Signed-off-by: Richard Palethorpe <io@richiejp.com>
1 parent 109f29c commit b171610

File tree

7 files changed

+64
-9
lines changed

7 files changed

+64
-9
lines changed

core/gallery/backends_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"github.com/mudler/LocalAI/pkg/system"
1313
. "github.com/onsi/ginkgo/v2"
1414
. "github.com/onsi/gomega"
15-
"gopkg.in/yaml.v2"
15+
"gopkg.in/yaml.v3"
1616
)
1717

1818
const (

core/gallery/gallery.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"github.com/mudler/LocalAI/pkg/xsync"
1717
"github.com/mudler/xlog"
1818

19-
"gopkg.in/yaml.v2"
19+
"gopkg.in/yaml.v3"
2020
)
2121

2222
func GetGalleryConfigFromURL[T any](url string, basePath string) (T, error) {

core/gallery/gallery_test.go

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import (
44
"os"
55
"path/filepath"
66

7+
"dario.cat/mergo"
78
"github.com/mudler/LocalAI/core/config"
89
. "github.com/mudler/LocalAI/core/gallery"
910
. "github.com/onsi/ginkgo/v2"
1011
. "github.com/onsi/gomega"
11-
"gopkg.in/yaml.v2"
12+
"gopkg.in/yaml.v3"
1213
)
1314

1415
var _ = Describe("Gallery", func() {
@@ -462,4 +463,60 @@ var _ = Describe("Gallery", func() {
462463
Expect(result).To(BeNil())
463464
})
464465
})
466+
467+
Describe("YAML merge with nested maps", func() {
468+
It("should handle YAML anchors and merges with nested overrides (regression test for nanbeige4.1)", func() {
469+
// This tests the fix for the panic that occurred with yaml.v2:
470+
// yaml.v2 produces map[interface{}]interface{} for nested maps
471+
// which caused mergo.Merge to panic with "value of type interface {} is not assignable to type string"
472+
// The exact YAML structure from gallery/index.yaml nanbeige4.1 entries
473+
yamlContent := `---
474+
- &nanbeige4
475+
name: "nanbeige4.1-3b-q8"
476+
overrides:
477+
parameters:
478+
model: nanbeige4.1-3b-q8_0.gguf
479+
- !!merge <<: *nanbeige4
480+
name: "nanbeige4.1-3b-q4"
481+
overrides:
482+
parameters:
483+
model: nanbeige4.1-3b-q4_k_m.gguf
484+
`
485+
var models []GalleryModel
486+
err := yaml.Unmarshal([]byte(yamlContent), &models)
487+
Expect(err).NotTo(HaveOccurred())
488+
Expect(models).To(HaveLen(2))
489+
490+
// Verify first model
491+
Expect(models[0].Name).To(Equal("nanbeige4.1-3b-q8"))
492+
Expect(models[0].Overrides).NotTo(BeNil())
493+
Expect(models[0].Overrides["parameters"]).To(BeAssignableToTypeOf(map[string]interface{}{}))
494+
params := models[0].Overrides["parameters"].(map[string]interface{})
495+
Expect(params["model"]).To(Equal("nanbeige4.1-3b-q8_0.gguf"))
496+
497+
// Verify second model (merged)
498+
Expect(models[1].Name).To(Equal("nanbeige4.1-3b-q4"))
499+
Expect(models[1].Overrides).NotTo(BeNil())
500+
Expect(models[1].Overrides["parameters"]).To(BeAssignableToTypeOf(map[string]interface{}{}))
501+
params = models[1].Overrides["parameters"].(map[string]interface{})
502+
Expect(params["model"]).To(Equal("nanbeige4.1-3b-q4_k_m.gguf"))
503+
504+
// Simulate the mergo.Merge call that was failing in models.go:251
505+
// This should not panic with yaml.v3
506+
configMap := make(map[string]interface{})
507+
configMap["name"] = "test"
508+
configMap["backend"] = "llama-cpp"
509+
configMap["parameters"] = map[string]interface{}{
510+
"model": "original.gguf",
511+
}
512+
513+
err = mergo.Merge(&configMap, models[1].Overrides, mergo.WithOverride)
514+
Expect(err).NotTo(HaveOccurred())
515+
Expect(configMap["parameters"]).NotTo(BeNil())
516+
517+
// Verify the merge worked correctly
518+
mergedParams := configMap["parameters"].(map[string]interface{})
519+
Expect(mergedParams["model"]).To(Equal("nanbeige4.1-3b-q4_k_m.gguf"))
520+
})
521+
})
465522
})

core/services/backends_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
. "github.com/onsi/ginkgo/v2"
1414
. "github.com/onsi/gomega"
15-
"gopkg.in/yaml.v2"
15+
"gopkg.in/yaml.v3"
1616
)
1717

1818
var _ = Describe("InstallExternalBackend", func() {

core/services/models.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
"github.com/mudler/LocalAI/pkg/system"
1414
"github.com/mudler/LocalAI/pkg/utils"
1515
"github.com/mudler/xlog"
16-
"gopkg.in/yaml.v2"
16+
"gopkg.in/yaml.v3"
1717
)
1818

1919
const (

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ require (
6161
go.opentelemetry.io/otel/metric v1.40.0
6262
go.opentelemetry.io/otel/sdk/metric v1.40.0
6363
google.golang.org/grpc v1.78.0
64-
gopkg.in/yaml.v2 v2.4.0
64+
google.golang.org/protobuf v1.36.11
6565
gopkg.in/yaml.v3 v3.0.1
6666
oras.land/oras-go/v2 v2.6.0
6767
)
@@ -75,7 +75,7 @@ require (
7575
github.com/tidwall/pretty v1.2.1 // indirect
7676
github.com/tidwall/sjson v1.2.5 // indirect
7777
github.com/valyala/fasttemplate v1.2.2 // indirect
78-
google.golang.org/protobuf v1.36.11 // indirect
78+
gopkg.in/yaml.v2 v2.4.0 // indirect
7979
)
8080

8181
require (

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,8 +509,6 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P
509509
github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
510510
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
511511
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
512-
github.com/mudler/cogito v0.8.1 h1:66qPJkAMrq/Vo8AC/PvXWuVxYPhi7X2DQuJIilL8+3I=
513-
github.com/mudler/cogito v0.8.1/go.mod h1:6sfja3lcu2nWRzEc0wwqGNu/eCG3EWgij+8s7xyUeQ4=
514512
github.com/mudler/cogito v0.8.2-0.20260214201734-da0d4ceb2b44 h1:joGszpItINnZdoL/0p2077Wz2xnxMGRSRgYN5mS7I4c=
515513
github.com/mudler/cogito v0.8.2-0.20260214201734-da0d4ceb2b44/go.mod h1:6sfja3lcu2nWRzEc0wwqGNu/eCG3EWgij+8s7xyUeQ4=
516514
github.com/mudler/edgevpn v0.31.1 h1:7qegiDWd0kAg6ljhNHxqvp8hbo/6BbzSdbb7/2WZfiY=

0 commit comments

Comments
 (0)