mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-02 19:49:07 +02:00
Ollama Cloud provider integration and backend test coverage
This commit is contained in:
@@ -487,6 +487,27 @@ var providers = []Provider{
|
|||||||
{ID: "kimi-k3", Label: "Kimi K3", InputPer1k: 0.003, OutputPer1k: 0.015, ContextWindow: 1000000},
|
{ID: "kimi-k3", Label: "Kimi K3", InputPer1k: 0.003, OutputPer1k: 0.015, ContextWindow: 1000000},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Direct Ollama Cloud uses the hosted OpenAI-compatible /v1 API.
|
||||||
|
// Models are discovered dynamically because the Cloud catalog changes
|
||||||
|
// independently of NetBird releases. ParserID intentionally remains
|
||||||
|
// empty to preserve the routing behavior of Ollama, vLLM, and custom
|
||||||
|
// OpenAI-compatible providers.
|
||||||
|
ID: "ollama_cloud",
|
||||||
|
Kind: KindProvider,
|
||||||
|
AuthMode: AuthModeRequired,
|
||||||
|
Name: "Ollama Cloud",
|
||||||
|
Description: "Hosted Ollama models via the OpenAI-compatible API",
|
||||||
|
DefaultHost: "ollama.com",
|
||||||
|
AuthHeaderName: "Authorization",
|
||||||
|
AuthHeaderTemplate: "Bearer ${API_KEY}",
|
||||||
|
DefaultContentType: "application/json",
|
||||||
|
BrandColor: "#000000",
|
||||||
|
Models: []Model{},
|
||||||
|
ModelDiscovery: &ModelDiscovery{
|
||||||
|
OllamaFallback: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ID: "litellm_proxy",
|
ID: "litellm_proxy",
|
||||||
Kind: KindGateway,
|
Kind: KindGateway,
|
||||||
|
|||||||
@@ -35,10 +35,41 @@ func TestOllamaCatalogEntry(t *testing.T) {
|
|||||||
assert.Empty(t, wire.Models)
|
assert.Empty(t, wire.Models)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOnlyOllamaSupportsModelDiscovery(t *testing.T) {
|
func TestOllamaCloudCatalogEntry(t *testing.T) {
|
||||||
|
entry, ok := Lookup("ollama_cloud")
|
||||||
|
require.True(t, ok, "Ollama Cloud must be available as a dedicated catalog provider")
|
||||||
|
|
||||||
|
assert.Equal(t, KindProvider, entry.Kind)
|
||||||
|
assert.Equal(t, AuthModeRequired, entry.EffectiveAuthMode())
|
||||||
|
assert.Equal(t, "Ollama Cloud", entry.Name)
|
||||||
|
assert.Equal(t, "Hosted Ollama models via the OpenAI-compatible API", entry.Description)
|
||||||
|
assert.Equal(t, "ollama.com", entry.DefaultHost)
|
||||||
|
assert.Equal(t, "Authorization", entry.AuthHeaderName)
|
||||||
|
assert.Equal(t, "Bearer ${API_KEY}", entry.AuthHeaderTemplate)
|
||||||
|
assert.Equal(t, "application/json", entry.DefaultContentType)
|
||||||
|
assert.Empty(t, entry.ParserID, "Ollama Cloud preserves the untagged Ollama/vLLM/custom routing behavior")
|
||||||
|
assert.Empty(t, entry.Models, "Ollama Cloud models are discovered dynamically")
|
||||||
|
require.NotNil(t, entry.ModelDiscovery)
|
||||||
|
assert.True(t, entry.ModelDiscovery.OllamaFallback)
|
||||||
|
|
||||||
|
wire := entry.ToAPIResponse()
|
||||||
|
assert.Equal(t, "ollama_cloud", wire.Id)
|
||||||
|
assert.Equal(t, api.AgentNetworkCatalogProviderKindProvider, wire.Kind)
|
||||||
|
assert.Equal(t, api.AgentNetworkCatalogProviderAuthModeRequired, wire.AuthMode)
|
||||||
|
assert.Equal(t, "ollama.com", wire.DefaultHost)
|
||||||
|
assert.True(t, wire.SupportsModelDiscovery)
|
||||||
|
assert.NotNil(t, wire.Models)
|
||||||
|
assert.Empty(t, wire.Models)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOnlyOllamaProvidersSupportModelDiscovery(t *testing.T) {
|
||||||
|
discoverable := map[string]bool{
|
||||||
|
"ollama": true,
|
||||||
|
"ollama_cloud": true,
|
||||||
|
}
|
||||||
for _, entry := range All() {
|
for _, entry := range All() {
|
||||||
supportsDiscovery := entry.ModelDiscovery != nil
|
supportsDiscovery := entry.ModelDiscovery != nil
|
||||||
assert.Equal(t, entry.ID == "ollama", supportsDiscovery, entry.ID)
|
assert.Equal(t, discoverable[entry.ID], supportsDiscovery, entry.ID)
|
||||||
assert.Equal(t, supportsDiscovery, entry.ToAPIResponse().SupportsModelDiscovery, entry.ID)
|
assert.Equal(t, supportsDiscovery, entry.ToAPIResponse().SupportsModelDiscovery, entry.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,74 @@ func TestDiscoverProviderModelsUsesPersistedProviderAndCluster(t *testing.T) {
|
|||||||
}, result.Models)
|
}, result.Models)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDiscoverProviderModelsSupportsOllamaCloud(t *testing.T) {
|
||||||
|
manager, mockStore, mockPermissions, mockProxy := newModelDiscoveryManager(t)
|
||||||
|
allowModelDiscovery(mockPermissions)
|
||||||
|
|
||||||
|
provider := &types.Provider{
|
||||||
|
ID: "provider-cloud",
|
||||||
|
AccountID: "account-1",
|
||||||
|
ProviderID: "ollama_cloud",
|
||||||
|
UpstreamURL: "https://ollama.com",
|
||||||
|
APIKey: "ollama-cloud-key",
|
||||||
|
}
|
||||||
|
mockStore.EXPECT().
|
||||||
|
GetAgentNetworkProviderByID(gomock.Any(), store.LockingStrengthNone, "account-1", provider.ID).
|
||||||
|
Return(provider, nil)
|
||||||
|
mockStore.EXPECT().
|
||||||
|
GetAgentNetworkSettings(gomock.Any(), store.LockingStrengthNone, "account-1").
|
||||||
|
Return(&types.Settings{AccountID: "account-1", Cluster: "cloud-proxies.example.com"}, nil)
|
||||||
|
mockProxy.EXPECT().
|
||||||
|
DiscoverModels(gomock.Any(), "account-1", "cloud-proxies.example.com", gomock.Any()).
|
||||||
|
DoAndReturn(func(_ context.Context, _, _ string, request *proto.ModelDiscoveryRequest) (*proto.ModelDiscoveryResult, error) {
|
||||||
|
assert.Equal(t, "https://ollama.com", request.UpstreamUrl)
|
||||||
|
assert.Equal(t, "Authorization", request.AuthHeaderName)
|
||||||
|
assert.Equal(t, "Bearer ollama-cloud-key", request.AuthHeaderValue)
|
||||||
|
assert.False(t, request.SkipTlsVerify)
|
||||||
|
assert.True(t, request.OllamaFallback)
|
||||||
|
return &proto.ModelDiscoveryResult{
|
||||||
|
RequestId: "probe-cloud",
|
||||||
|
Source: "openai_v1_models",
|
||||||
|
Models: []*proto.ModelDiscoveryModel{
|
||||||
|
{Id: "gpt-oss:120b", Label: "gpt-oss:120b"},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
result, err := manager.DiscoverProviderModels(context.Background(), "account-1", "user-1", provider.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "probe-cloud", result.RequestID)
|
||||||
|
assert.Equal(t, "cloud-proxies.example.com", result.ProxyCluster)
|
||||||
|
assert.Equal(t, []types.DiscoveredModel{
|
||||||
|
{ID: "gpt-oss:120b", Label: "gpt-oss:120b"},
|
||||||
|
}, result.Models)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiscoverProviderModelsRejectsOllamaCloudWithoutKey(t *testing.T) {
|
||||||
|
manager, mockStore, mockPermissions, _ := newModelDiscoveryManager(t)
|
||||||
|
allowModelDiscovery(mockPermissions)
|
||||||
|
|
||||||
|
provider := &types.Provider{
|
||||||
|
ID: "provider-cloud",
|
||||||
|
AccountID: "account-1",
|
||||||
|
ProviderID: "ollama_cloud",
|
||||||
|
UpstreamURL: "https://ollama.com",
|
||||||
|
}
|
||||||
|
mockStore.EXPECT().
|
||||||
|
GetAgentNetworkProviderByID(gomock.Any(), store.LockingStrengthNone, "account-1", provider.ID).
|
||||||
|
Return(provider, nil)
|
||||||
|
mockStore.EXPECT().
|
||||||
|
GetAgentNetworkSettings(gomock.Any(), store.LockingStrengthNone, "account-1").
|
||||||
|
Return(&types.Settings{AccountID: "account-1", Cluster: "cloud-proxies.example.com"}, nil)
|
||||||
|
|
||||||
|
_, err := manager.DiscoverProviderModels(context.Background(), "account-1", "user-1", provider.ID)
|
||||||
|
require.Error(t, err)
|
||||||
|
statusErr, ok := status.FromError(err)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, status.PreconditionFailed, statusErr.Type())
|
||||||
|
assert.Contains(t, err.Error(), "authentication is not configured correctly")
|
||||||
|
}
|
||||||
|
|
||||||
func TestDiscoverProviderModelsRejectsUnsupportedProvider(t *testing.T) {
|
func TestDiscoverProviderModelsRejectsUnsupportedProvider(t *testing.T) {
|
||||||
manager, mockStore, mockPermissions, _ := newModelDiscoveryManager(t)
|
manager, mockStore, mockPermissions, _ := newModelDiscoveryManager(t)
|
||||||
allowModelDiscovery(mockPermissions)
|
allowModelDiscovery(mockPermissions)
|
||||||
|
|||||||
@@ -18,6 +18,23 @@ func TestPrepareProviderAPIKey(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "api_key is required")
|
assert.Contains(t, err.Error(), "api_key is required")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Ollama Cloud requires a key", func(t *testing.T) {
|
||||||
|
provider := &types.Provider{ProviderID: "ollama_cloud"}
|
||||||
|
err := prepareProviderAPIKey(provider, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "api_key is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Ollama Cloud accepts a key", func(t *testing.T) {
|
||||||
|
provider := &types.Provider{
|
||||||
|
ProviderID: "ollama_cloud",
|
||||||
|
APIKey: "ollama-cloud-key",
|
||||||
|
APIKeyProvided: true,
|
||||||
|
}
|
||||||
|
require.NoError(t, prepareProviderAPIKey(provider, nil))
|
||||||
|
assert.Equal(t, "ollama-cloud-key", provider.APIKey)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("optional create accepts an empty key", func(t *testing.T) {
|
t.Run("optional create accepts an empty key", func(t *testing.T) {
|
||||||
provider := &types.Provider{ProviderID: "ollama"}
|
provider := &types.Provider{ProviderID: "ollama"}
|
||||||
require.NoError(t, prepareProviderAPIKey(provider, nil))
|
require.NoError(t, prepareProviderAPIKey(provider, nil))
|
||||||
|
|||||||
@@ -1275,3 +1275,46 @@ func TestSynthesizeServices_OllamaOptionalAPIKey(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSynthesizeServices_OllamaCloudRoute(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
mockStore := store.NewMockStore(ctrl)
|
||||||
|
|
||||||
|
provider := newSynthTestProvider()
|
||||||
|
provider.ProviderID = "ollama_cloud"
|
||||||
|
provider.Name = "Ollama Cloud"
|
||||||
|
provider.UpstreamURL = "https://ollama.com"
|
||||||
|
provider.APIKey = "ollama-cloud-key"
|
||||||
|
provider.Models = []types.ProviderModel{{ID: "gpt-oss:120b"}}
|
||||||
|
policy := newSynthTestPolicy(provider.ID, "grp-eng", "")
|
||||||
|
|
||||||
|
expectSynthBaseInputs(mockStore, ctx, newSynthTestSettings(),
|
||||||
|
[]*types.Provider{provider},
|
||||||
|
[]*types.Policy{policy},
|
||||||
|
[]*types.Guardrail{})
|
||||||
|
|
||||||
|
services, err := SynthesizeServices(ctx, mockStore, testAccountID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, services, 1)
|
||||||
|
|
||||||
|
var routerCfg routerConfig
|
||||||
|
for _, middleware := range services[0].Targets[0].Options.Middlewares {
|
||||||
|
if middleware.ID == middlewareIDLLMRouter {
|
||||||
|
require.NoError(t, json.Unmarshal(middleware.ConfigJSON, &routerCfg))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Len(t, routerCfg.Providers, 1)
|
||||||
|
|
||||||
|
route := routerCfg.Providers[0]
|
||||||
|
assert.Empty(t, route.Vendor, "Ollama Cloud preserves the untagged Ollama/vLLM/custom routing behavior")
|
||||||
|
assert.Equal(t, []string{"gpt-oss:120b"}, route.Models)
|
||||||
|
assert.Equal(t, "https", route.UpstreamScheme)
|
||||||
|
assert.Equal(t, "ollama.com", route.UpstreamHost)
|
||||||
|
assert.Empty(t, route.UpstreamPath)
|
||||||
|
assert.Equal(t, "Authorization", route.AuthHeaderName)
|
||||||
|
assert.Equal(t, "Bearer ollama-cloud-key", route.AuthHeaderValue)
|
||||||
|
assert.False(t, route.SkipTLSVerify)
|
||||||
|
}
|
||||||
|
|||||||
@@ -210,9 +210,9 @@ func (d *Discoverer) fetch(ctx context.Context, in Request, endpointPath string)
|
|||||||
}
|
}
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
if in.AuthHeaderName != "" {
|
if in.AuthHeaderName != "" {
|
||||||
// Phase 3 is Ollama-only. Canonicalizing the sole catalog-owned
|
// Discovery is currently enabled only for Ollama-compatible providers.
|
||||||
// credential header keeps the control message from becoming a generic
|
// Canonicalizing the sole catalog-owned credential header keeps the
|
||||||
// arbitrary-header primitive.
|
// control message from becoming a generic arbitrary-header primitive.
|
||||||
req.Header.Set("Authorization", in.AuthHeaderValue)
|
req.Header.Set("Authorization", in.AuthHeaderValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user