Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/maximhq/bifrost/core/providers/bedrock"
"github.com/maximhq/bifrost/core/providers/cerebras"
"github.com/maximhq/bifrost/core/providers/cohere"
"github.com/maximhq/bifrost/core/providers/edenai"
"github.com/maximhq/bifrost/core/providers/elevenlabs"
"github.com/maximhq/bifrost/core/providers/fireworks"
"github.com/maximhq/bifrost/core/providers/gemini"
Expand Down Expand Up @@ -4056,6 +4057,8 @@ func (bifrost *Bifrost) createBaseProvider(providerKey schemas.ModelProvider, co
return runware.NewRunwareProvider(config, bifrost.logger)
case schemas.Fireworks:
return fireworks.NewFireworksProvider(config, bifrost.logger)
case schemas.EdenAI:
return edenai.NewEdenAIProvider(config, bifrost.logger)
default:
return nil, fmt.Errorf("unsupported provider: %s", targetProviderKey)
}
Expand Down
389 changes: 389 additions & 0 deletions core/providers/edenai/edenai.go

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions core/providers/edenai/edenai_live_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package edenai_test

import (
"context"
"os"
"testing"

bifrost "github.com/maximhq/bifrost/core"
"github.com/maximhq/bifrost/core/providers/edenai"
"github.com/maximhq/bifrost/core/schemas"
)

// TestEdenAILiveChatCompletion performs a real chat completion against the Eden AI
// API through the Bifrost provider. It is skipped unless EDENAI_API_KEY is set,
// so CI stays hermetic.
func TestEdenAILiveChatCompletion(t *testing.T) {
apiKey := os.Getenv("EDENAI_API_KEY")
if apiKey == "" {
t.Skip("EDENAI_API_KEY not set; skipping live Eden AI test")
}

config := &schemas.ProviderConfig{}
config.CheckAndSetDefaults()
provider, err := edenai.NewEdenAIProvider(config, bifrost.NewNoOpLogger())
if err != nil {
t.Fatalf("NewEdenAIProvider failed: %v", err)
}

ctx := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline)
key := schemas.Key{Value: *schemas.NewSecretVar(apiKey), Models: schemas.WhiteList{"*"}}
content := "Reply with exactly: BIFROST-EDEN-OK"
request := &schemas.BifrostChatRequest{
Provider: schemas.EdenAI,
Model: "openai/gpt-4o-mini",
Input: []schemas.ChatMessage{
{Role: schemas.ChatMessageRoleUser, Content: &schemas.ChatMessageContent{ContentStr: &content}},
},
}

resp, bifErr := provider.ChatCompletion(ctx, key, request)
if bifErr != nil {
msg := ""
if bifErr.Error != nil {
msg = bifErr.Error.Message
}
t.Fatalf("live ChatCompletion returned error: %s", msg)
}
if resp == nil || len(resp.Choices) == 0 {
t.Fatalf("expected at least one choice in the response, got %+v", resp)
}
t.Logf("Eden AI live chat completion succeeded via Bifrost provider (%d choice(s))", len(resp.Choices))
}
43 changes: 43 additions & 0 deletions core/providers/edenai/edenai_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package edenai

import (
"testing"

"github.com/maximhq/bifrost/core/schemas"
)

// Compile-time check that edenaiProvider satisfies the full Provider interface.
var _ schemas.Provider = (*edenaiProvider)(nil)

func TestEdenAIProviderConstructorDefaults(t *testing.T) {
t.Parallel()

config := &schemas.ProviderConfig{}
config.CheckAndSetDefaults()
provider, err := NewEdenAIProvider(config, nil)
if err != nil {
t.Fatalf("NewEdenAIProvider failed: %v", err)
}
if provider.GetProviderKey() != schemas.EdenAI {
t.Errorf("expected provider key %s, got %s", schemas.EdenAI, provider.GetProviderKey())
}
if provider.networkConfig.BaseURL != "https://api.edenai.run/v3" {
t.Errorf("expected base URL https://api.edenai.run/v3, got %s", provider.networkConfig.BaseURL)
}
}

func TestEdenAIUnsupportedOperations(t *testing.T) {
t.Parallel()

p := &edenaiProvider{}

if _, err := p.TextCompletion(nil, schemas.Key{}, nil); err == nil {
t.Error("expected TextCompletion to be unsupported")
}
if _, err := p.Embedding(nil, schemas.Key{}, nil); err == nil {
t.Error("expected Embedding to be unsupported")
}
if _, err := p.Speech(nil, schemas.Key{}, nil); err == nil {
t.Error("expected Speech to be unsupported")
}
}
Comment on lines +29 to +43

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Sparse unsupported-operation coverage

Only 3 of the ~30 unsupported operations are asserted. If a future refactor accidentally wires one of the others (e.g. Embedding, Rerank, ImageGeneration) to a real implementation path, the test suite won't catch the regression. The repo pattern for new providers uses table-driven sweeps over all unsupported ops — e.g. looping over a slice of func() *schemas.BifrostError calls and asserting each returns non-nil. Also note that the repo's make test-core target runs the shared provider scenario suite; the PR description references bare go test ./core/providers/edenai/..., which skips that broader coverage.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

2 changes: 2 additions & 0 deletions core/schemas/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const (
Runway ModelProvider = "runway"
Runware ModelProvider = "runware"
Fireworks ModelProvider = "fireworks"
EdenAI ModelProvider = "edenai"
)

// SupportedBaseProviders is the list of base providers allowed for custom providers.
Expand Down Expand Up @@ -108,6 +109,7 @@ var StandardProviders = []ModelProvider{
Runway,
Runware,
Fireworks,
EdenAI,
}

// RequestType represents the type of request being made to a provider.
Expand Down
1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
"providers/supported-providers/cerebras",
"providers/supported-providers/cohere",
"providers/supported-providers/databricks",
"providers/supported-providers/edenai",
"providers/supported-providers/elevenlabs",
"providers/supported-providers/fireworks",
"providers/supported-providers/gemini",
Expand Down
85 changes: 85 additions & 0 deletions docs/providers/supported-providers/edenai.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
title: "Eden AI"
description: "Eden AI integration guide - a European, OpenAI-compatible gateway to 100+ models with a single key."
icon: "globe"
---

## Overview

[Eden AI](https://www.edenai.co/) is a **European, OpenAI-compatible** gateway that provides access to 100+ models from many providers (Mistral, OpenAI, Anthropic, Google, Cohere, DeepSeek, and more) through a single unified API and one key. Bifrost delegates to the OpenAI implementation, pointing at Eden's endpoint (`https://api.edenai.run/v3`). Key points:

- **One key, many providers** — route to models from many vendors through a single `EDENAI_API_KEY`.
- **EU-based gateway** — requests are served through an Europe-based gateway with GDPR-aligned data handling, useful for data-residency requirements.
- **OpenAI-compatible** — chat completions, streaming (SSE), tool calling, and `reasoning_effort` are handled through the OpenAI-compatible surface.
- **Model naming** — models keep the underlying `provider/model` form, e.g. `mistral/mistral-small-latest`, `openai/gpt-4o-mini`, `anthropic/claude-sonnet-4-5`.

### Supported Operations

| Operation | Non-Streaming | Streaming | Endpoint |
|-----------|---------------|-----------|----------|
| Chat Completions | ✅ | ✅ | `/chat/completions` |
| Responses API | ✅ | ✅ | (delegated to Chat Completions) |
| List Models | ✅ | - | `/models` |
| Text Completions | ❌ | ❌ | - |
| Embeddings | ❌ | ❌ | - |
| Image Generation | ❌ | ❌ | - |
| Speech (TTS) | ❌ | ❌ | - |
| Transcriptions (STT) | ❌ | ❌ | - |

<Note>
Eden AI exposes its API under the `/v3` base path (`https://api.edenai.run/v3`), so the chat completions endpoint is `https://api.edenai.run/v3/chat/completions`. Errors are returned in the standard OpenAI error envelope.
</Note>

## Setup & Configuration

Configure Eden AI as a provider.

<Tabs>
<Tab title="Web UI">

1. Navigate to **Models** > **Model Providers**. If **Eden AI** is not listed under **Configured Providers**, click **Add New Provider** and select **Eden AI**.
2. Click **Add Key** or edit an existing key.
3. Set a name for your key.
4. Paste your API key directly or use an environment variable (for example, `env.EDENAI_API_KEY`).
5. Set **Allowed Models** to **All Models** (default) or a specific allowlist.
6. Save the provider configuration.

</Tab>
<Tab title="config.json">

```json
{
"providers": {
"edenai": {
"keys": [
{
"name": "edenai-key-1",
"value": "env.EDENAI_API_KEY",
"models": [
"*"
],
"weight": 1.0
}
]
}
}
}
```

The default base URL is `https://api.edenai.run/v3`. You can override it via the provider's `network_config.base_url` if needed.

</Tab>
</Tabs>

## Usage

```bash
curl -X POST http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "edenai/mistral/mistral-small-latest",
"messages": [{"role": "user", "content": "Hello from a sovereign EU stack!"}]
}'
```

Get an API key from your [Eden AI account](https://www.edenai.co/) and see the [Eden AI documentation](https://docs.edenai.co/) for the full list of available models.
1 change: 1 addition & 0 deletions docs/providers/supported-providers/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ The following table summarizes which operations are supported by each provider v
| Ollama (`ollama/<model>`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| OpenAI (`openai/<model>`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| OpenRouter (`openrouter/<model>`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Eden AI (`edenai/<model>`) | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Parasail (`parasail/<model>`) | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Perplexity (`perplexity/<model>`) | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Replicate (`replicate/<model>`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
Expand Down
6 changes: 5 additions & 1 deletion transports/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,9 @@
"openrouter": {
"$ref": "#/$defs/provider"
},
"edenai": {
"$ref": "#/$defs/provider"
},
"sgl": {
"$ref": "#/$defs/provider_with_sgl_config"
},
Expand Down Expand Up @@ -5459,7 +5462,8 @@
"vllm",
"runway",
"runware",
"fireworks"
"fireworks",
"edenai"
],
"description": "Base provider type to extend"
},
Expand Down