mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-27 16:32:34 -04: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},
|
||||
},
|
||||
},
|
||||
{
|
||||
// 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",
|
||||
Kind: KindGateway,
|
||||
|
||||
@@ -35,10 +35,41 @@ func TestOllamaCatalogEntry(t *testing.T) {
|
||||
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() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,74 @@ func TestDiscoverProviderModelsUsesPersistedProviderAndCluster(t *testing.T) {
|
||||
}, 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) {
|
||||
manager, mockStore, mockPermissions, _ := newModelDiscoveryManager(t)
|
||||
allowModelDiscovery(mockPermissions)
|
||||
|
||||
@@ -18,6 +18,23 @@ func TestPrepareProviderAPIKey(t *testing.T) {
|
||||
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) {
|
||||
provider := &types.Provider{ProviderID: "ollama"}
|
||||
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")
|
||||
if in.AuthHeaderName != "" {
|
||||
// Phase 3 is Ollama-only. Canonicalizing the sole catalog-owned
|
||||
// credential header keeps the control message from becoming a generic
|
||||
// arbitrary-header primitive.
|
||||
// Discovery is currently enabled only for Ollama-compatible providers.
|
||||
// Canonicalizing the sole catalog-owned credential header keeps the
|
||||
// control message from becoming a generic arbitrary-header primitive.
|
||||
req.Header.Set("Authorization", in.AuthHeaderValue)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user