fix(edge): 프로필 backing 중복을 단일화한다
This commit is contained in:
parent
1789f4007c
commit
7d8551b440
2 changed files with 128 additions and 1 deletions
|
|
@ -19,7 +19,8 @@ import (
|
|||
// Providers with a non-empty Adapter field and no protocol profile reference a
|
||||
// legacy instance and are skipped during provider-first compile. A profile-
|
||||
// backed provider is compiled under its provider id from the referenced HTTP
|
||||
// adapter while the legacy instance is retained below.
|
||||
// adapter. When that backing instance has the same key, it is consumed instead
|
||||
// of being emitted a second time.
|
||||
//
|
||||
// After config normalisation, OllamaInstances, VllmInstances, and
|
||||
// OpenAICompatInstances already contain the promoted legacy single-instance
|
||||
|
|
@ -45,6 +46,11 @@ func BuildConfigPayload(rec *NodeRecord) (*iop.NodeConfigPayload, error) {
|
|||
|
||||
// flat namespace to detect conflicts between provider ids and legacy adapter keys
|
||||
seenFlat := make(map[string]struct{})
|
||||
// A profile-backed provider may intentionally reuse the exact legacy
|
||||
// adapter key that supplies its transport settings. Config validation has
|
||||
// already required both definitions to agree; emit only the provider-backed
|
||||
// adapter so the Node receives one unambiguous instance key.
|
||||
shadowedLegacy := make(map[string]struct{})
|
||||
|
||||
for _, p := range rec.Providers {
|
||||
if p.Adapter != "" && p.RuntimeProfile == nil {
|
||||
|
|
@ -62,6 +68,18 @@ func BuildConfigPayload(rec *NodeRecord) (*iop.NodeConfigPayload, error) {
|
|||
var err error
|
||||
if p.RuntimeProfile != nil && strings.TrimSpace(p.Adapter) != "" {
|
||||
ac, err = profileBackedProviderToAdapterConfig(rec, p)
|
||||
if err == nil && strings.TrimSpace(p.Adapter) == p.ID {
|
||||
for _, inst := range rec.Adapters.VllmInstances {
|
||||
if inst.Name == p.ID {
|
||||
shadowedLegacy["vllm:"+p.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
for _, inst := range rec.Adapters.OpenAICompatInstances {
|
||||
if inst.Name == p.ID {
|
||||
shadowedLegacy["openai_compat:"+p.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ac, err = providerToAdapterConfig(p)
|
||||
}
|
||||
|
|
@ -83,6 +101,9 @@ func BuildConfigPayload(rec *NodeRecord) (*iop.NodeConfigPayload, error) {
|
|||
}
|
||||
seen[key] = struct{}{}
|
||||
if _, conflict := seenFlat[inst.Name]; conflict {
|
||||
if _, shadowed := shadowedLegacy[key]; shadowed {
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("ollama adapter instance key %q conflicts with provider id", inst.Name)
|
||||
}
|
||||
payload.Adapters = append(payload.Adapters, &iop.AdapterConfig{
|
||||
|
|
@ -112,6 +133,9 @@ func BuildConfigPayload(rec *NodeRecord) (*iop.NodeConfigPayload, error) {
|
|||
}
|
||||
seen[key] = struct{}{}
|
||||
if _, conflict := seenFlat[inst.Name]; conflict {
|
||||
if _, shadowed := shadowedLegacy[key]; shadowed {
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("vllm adapter instance key %q conflicts with provider id", inst.Name)
|
||||
}
|
||||
payload.Adapters = append(payload.Adapters, &iop.AdapterConfig{
|
||||
|
|
@ -140,6 +164,9 @@ func BuildConfigPayload(rec *NodeRecord) (*iop.NodeConfigPayload, error) {
|
|||
}
|
||||
seen[key] = struct{}{}
|
||||
if _, conflict := seenFlat[inst.Name]; conflict {
|
||||
if _, shadowed := shadowedLegacy[key]; shadowed {
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("openai_compat adapter instance key %q conflicts with provider id", inst.Name)
|
||||
}
|
||||
payload.Adapters = append(payload.Adapters, &iop.AdapterConfig{
|
||||
|
|
|
|||
|
|
@ -249,6 +249,106 @@ func TestProtocolProfileBuildConfigPayloadNoBase(t *testing.T) {
|
|||
_ = openaiCfg.GetProtocolProfile()
|
||||
}
|
||||
|
||||
func TestProtocolProfileProviderReusingBackingKeyEmitsOneAdapter(t *testing.T) {
|
||||
profile := config.ConcreteProtocolProfile{
|
||||
ID: "openai_chat",
|
||||
ProtocolProfileConf: config.ProtocolProfileConf{
|
||||
Driver: config.ProtocolDriverOpenAIChat,
|
||||
},
|
||||
}
|
||||
rec := &NodeRecord{
|
||||
ID: "node-profile-reuse",
|
||||
Alias: "profile-reuse",
|
||||
Token: "token",
|
||||
Providers: []config.NodeProviderConf{
|
||||
{
|
||||
ID: "provider-a",
|
||||
Type: "vllm",
|
||||
Adapter: "provider-a",
|
||||
Capacity: 2,
|
||||
RequestTimeoutMS: 120000,
|
||||
RuntimeProfile: &profile,
|
||||
},
|
||||
},
|
||||
Adapters: config.AdaptersConf{
|
||||
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
|
||||
{
|
||||
Name: "provider-a",
|
||||
Enabled: true,
|
||||
Provider: "vllm",
|
||||
Endpoint: "http://127.0.0.1:8000/v1",
|
||||
Capacity: 2,
|
||||
RequestTimeoutMS: 120000,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
payload, err := BuildConfigPayload(rec)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildConfigPayload: %v", err)
|
||||
}
|
||||
if len(payload.GetAdapters()) != 1 {
|
||||
t.Fatalf("adapter count = %d, want 1", len(payload.GetAdapters()))
|
||||
}
|
||||
adapter := payload.GetAdapters()[0]
|
||||
if adapter.GetName() != "provider-a" {
|
||||
t.Fatalf("adapter name = %q, want provider-a", adapter.GetName())
|
||||
}
|
||||
openAICompat := adapter.GetOpenaiCompat()
|
||||
if openAICompat == nil {
|
||||
t.Fatal("expected OpenAI-compatible adapter")
|
||||
}
|
||||
if openAICompat.GetEndpoint() != "http://127.0.0.1:8000/v1" {
|
||||
t.Fatalf("endpoint = %q", openAICompat.GetEndpoint())
|
||||
}
|
||||
if openAICompat.GetProtocolProfile() == nil {
|
||||
t.Fatal("expected resolved protocol profile")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtocolProfileProviderReusingBackingKeyRejectsOtherLegacyType(t *testing.T) {
|
||||
profile := config.ConcreteProtocolProfile{
|
||||
ID: "openai_chat",
|
||||
ProtocolProfileConf: config.ProtocolProfileConf{
|
||||
Driver: config.ProtocolDriverOpenAIChat,
|
||||
},
|
||||
}
|
||||
rec := &NodeRecord{
|
||||
ID: "node-profile-reuse-conflict",
|
||||
Alias: "profile-reuse-conflict",
|
||||
Token: "token",
|
||||
Providers: []config.NodeProviderConf{
|
||||
{
|
||||
ID: "provider-a",
|
||||
Type: "vllm",
|
||||
Adapter: "provider-a",
|
||||
Capacity: 2,
|
||||
RuntimeProfile: &profile,
|
||||
},
|
||||
},
|
||||
Adapters: config.AdaptersConf{
|
||||
OllamaInstances: []config.OllamaInstanceConf{
|
||||
{Name: "provider-a", Enabled: true, Capacity: 2},
|
||||
},
|
||||
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
|
||||
{
|
||||
Name: "provider-a",
|
||||
Enabled: true,
|
||||
Provider: "vllm",
|
||||
Endpoint: "http://127.0.0.1:8000/v1",
|
||||
Capacity: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := BuildConfigPayload(rec)
|
||||
if err == nil || !strings.Contains(err.Error(), "ollama adapter instance key") {
|
||||
t.Fatalf("BuildConfigPayload error = %v, want ollama key conflict", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtocolProfileAdapterBackingCompile(t *testing.T) {
|
||||
profile := config.ConcreteProtocolProfile{
|
||||
ID: "custom", ProtocolProfileConf: config.ProtocolProfileConf{
|
||||
|
|
|
|||
Loading…
Reference in a new issue