Skip to content

Commit 4e9ea64

Browse files
committed
feat(providers): add Eden AI as an OpenAI-compatible provider
Eden AI (https://www.edenai.co) is an EU-hosted, OpenAI-compatible gateway exposing 100+ models from many providers through a single endpoint and API key. Models use the provider/model naming scheme (the full id is sent upstream, like OpenRouter). Adds a first-class provider (type: edenai) reusing the shared OpenAI-compatible transport, registers it in the factory, and documents it (config example + docs page). API key via EDENAI_API_KEY only; never hardcoded. Signed-off-by: Victor M. SMITH <72023257+MVS-source@users.noreply.github.com>
1 parent 20977e1 commit 4e9ea64

6 files changed

Lines changed: 184 additions & 0 deletions

File tree

config/config.example.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,16 @@ providers:
385385
# - openai/gpt-oss-120b
386386
# - anthropic/claude-sonnet-4
387387

388+
# Example: Eden AI. EU-hosted, OpenAI-compatible gateway to 100+ models.
389+
# Models use the "provider/model" scheme; the full id is sent upstream.
390+
# edenai:
391+
# type: "edenai"
392+
# base_url: "https://api.edenai.run/v3"
393+
# api_key: "${EDENAI_API_KEY}"
394+
# models:
395+
# - anthropic/claude-sonnet-4-5
396+
# - mistral/codestral-latest
397+
388398
# Example: Azure OpenAI
389399
# azure:
390400
# type: "azure"

docs/docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
"providers/anthropic",
128128
"providers/gemini",
129129
"providers/deepseek",
130+
"providers/edenai",
130131
"providers/bailian",
131132
"providers/xiaomi",
132133
"providers/opencode-go",

docs/providers/edenai.mdx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
title: "Eden AI"
3+
description: "Configure Eden AI in GoModel — an EU-hosted, OpenAI-compatible gateway to 100+ models via a single API key."
4+
icon: "globe"
5+
---
6+
7+
[Eden AI](https://www.edenai.co) is an EU-hosted, OpenAI-compatible gateway that
8+
exposes 100+ models from many providers (OpenAI, Anthropic, Google, Mistral,
9+
DeepSeek, and more) through a single endpoint and API key. Models use the
10+
`provider/model` naming scheme (e.g. `anthropic/claude-sonnet-4-5`); GoModel
11+
sends the full id upstream unchanged.
12+
13+
## Configure
14+
15+
```bash
16+
EDENAI_API_KEY=...
17+
```
18+
19+
Or in `config.yaml`:
20+
21+
```yaml
22+
providers:
23+
edenai:
24+
type: edenai
25+
base_url: "https://api.edenai.run/v3"
26+
api_key: "${EDENAI_API_KEY}"
27+
models:
28+
- anthropic/claude-sonnet-4-5
29+
- mistral/codestral-latest
30+
```
31+
32+
<Note>
33+
The default endpoint is `https://api.edenai.run/v3`. For EU-only data
34+
residency, set `base_url` to `https://api.eu.edenai.run/v3`.
35+
</Note>
36+
37+
Because Eden AI is fully OpenAI-compatible, GoModel routes chat completions,
38+
embeddings, and model discovery through its shared OpenAI-compatible adapter
39+
with no request quirks.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Package edenai provides Eden AI integration for the LLM gateway.
2+
package edenai
3+
4+
import (
5+
"net/http"
6+
7+
"gomodel/internal/core"
8+
"gomodel/internal/llmclient"
9+
"gomodel/internal/providers"
10+
"gomodel/internal/providers/openai"
11+
)
12+
13+
// Eden AI (https://www.edenai.co) is an EU-hosted, OpenAI-compatible gateway
14+
// that exposes 100+ models from many providers through a single endpoint and
15+
// API key. Models use the "provider/model" naming scheme (e.g.
16+
// "anthropic/claude-sonnet-4-5"); the full id is sent upstream unchanged.
17+
const defaultBaseURL = "https://api.edenai.run/v3"
18+
19+
// Registration provides factory registration for the Eden AI provider.
20+
var Registration = providers.Registration{
21+
Type: "edenai",
22+
New: New,
23+
PassthroughSemanticEnricher: openai.Registration.PassthroughSemanticEnricher,
24+
Discovery: providers.DiscoveryConfig{
25+
DefaultBaseURL: defaultBaseURL,
26+
},
27+
}
28+
29+
// Provider implements the core.Provider interface for Eden AI. Eden AI's API
30+
// is OpenAI-compatible, so all transport goes through the shared compatible
31+
// provider with no request quirks.
32+
type Provider struct {
33+
*openai.CompatibleProvider
34+
}
35+
36+
var _ core.Provider = (*Provider)(nil)
37+
38+
// New creates a new Eden AI provider.
39+
func New(cfg providers.ProviderConfig, opts providers.ProviderOptions) core.Provider {
40+
return &Provider{
41+
CompatibleProvider: openai.NewCompatibleProvider(cfg.APIKey, opts, compatibleConfig(providers.ResolveBaseURL(cfg.BaseURL, defaultBaseURL))),
42+
}
43+
}
44+
45+
// NewWithHTTPClient creates a new Eden AI provider with a custom HTTP client.
46+
// If httpClient is nil, http.DefaultClient is used.
47+
func NewWithHTTPClient(apiKey string, baseURL string, httpClient *http.Client, hooks llmclient.Hooks) *Provider {
48+
return &Provider{
49+
CompatibleProvider: openai.NewCompatibleProviderWithHTTPClient(apiKey, httpClient, hooks, compatibleConfig(providers.ResolveBaseURL(baseURL, defaultBaseURL))),
50+
}
51+
}
52+
53+
func compatibleConfig(baseURL string) openai.CompatibleProviderConfig {
54+
return openai.CompatibleProviderConfig{
55+
ProviderName: "edenai",
56+
BaseURL: baseURL,
57+
SetHeaders: setHeaders,
58+
}
59+
}
60+
61+
func setHeaders(req *http.Request, apiKey string) {
62+
providers.SetAuthHeaders(req, apiKey, providers.AuthHeaderConfig{
63+
AuthScheme: "Bearer ",
64+
})
65+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package edenai
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"gomodel/internal/core"
10+
"gomodel/internal/llmclient"
11+
"gomodel/internal/providers"
12+
)
13+
14+
func TestRegistration(t *testing.T) {
15+
if Registration.Type != "edenai" {
16+
t.Fatalf("Registration.Type = %q, want edenai", Registration.Type)
17+
}
18+
if Registration.Discovery.DefaultBaseURL != defaultBaseURL {
19+
t.Fatalf("DefaultBaseURL = %q, want %q", Registration.Discovery.DefaultBaseURL, defaultBaseURL)
20+
}
21+
if p := New(providers.ProviderConfig{APIKey: "eden-key"}, providers.ProviderOptions{}); p == nil {
22+
t.Fatal("New() returned nil")
23+
}
24+
}
25+
26+
func TestChatCompletion_UsesBearerAuthAndChatEndpoint(t *testing.T) {
27+
var gotPath string
28+
var gotAuth string
29+
30+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
31+
gotPath = r.URL.Path
32+
gotAuth = r.Header.Get("Authorization")
33+
w.Header().Set("Content-Type", "application/json")
34+
_, _ = w.Write([]byte(`{
35+
"id":"chatcmpl-edenai",
36+
"created":1677652288,
37+
"model":"anthropic/claude-sonnet-4-5",
38+
"choices":[{"index":0,"message":{"role":"assistant","content":"hello"},"finish_reason":"stop"}],
39+
"usage":{"prompt_tokens":3,"completion_tokens":1,"total_tokens":4}
40+
}`))
41+
}))
42+
defer server.Close()
43+
44+
provider := NewWithHTTPClient("eden-key", server.URL, server.Client(), llmclient.Hooks{})
45+
46+
resp, err := provider.ChatCompletion(context.Background(), &core.ChatRequest{
47+
Model: "anthropic/claude-sonnet-4-5",
48+
Messages: []core.Message{
49+
{Role: "user", Content: "hi"},
50+
},
51+
})
52+
if err != nil {
53+
t.Fatalf("ChatCompletion() error = %v", err)
54+
}
55+
if resp.Model != "anthropic/claude-sonnet-4-5" {
56+
t.Fatalf("resp.Model = %q, want anthropic/claude-sonnet-4-5", resp.Model)
57+
}
58+
if resp.Usage.TotalTokens != 4 {
59+
t.Fatalf("resp.Usage = %+v, want total_tokens=4", resp.Usage)
60+
}
61+
if gotPath != "/chat/completions" {
62+
t.Fatalf("path = %q, want /chat/completions", gotPath)
63+
}
64+
if gotAuth != "Bearer eden-key" {
65+
t.Fatalf("Authorization = %q, want Bearer eden-key", gotAuth)
66+
}
67+
}

run/providers.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"gomodel/internal/providers/bailian"
1010
"gomodel/internal/providers/bedrock"
1111
"gomodel/internal/providers/deepseek"
12+
"gomodel/internal/providers/edenai"
1213
"gomodel/internal/providers/fireworks"
1314
"gomodel/internal/providers/gemini"
1415
"gomodel/internal/providers/groq"
@@ -43,6 +44,7 @@ func defaultProviderFactory(cfg *config.Config) *providers.ProviderFactory {
4344
factory.Add(anthropic.Registration)
4445
factory.Add(bedrock.Registration)
4546
factory.Add(deepseek.Registration)
47+
factory.Add(edenai.Registration)
4648
factory.Add(fireworks.Registration)
4749
factory.Add(gemini.Registration)
4850
factory.Add(vertex.Registration)

0 commit comments

Comments
 (0)