feat: add DaoXE as an OpenAI-compatible model provider (#19560)

Adds DaoXE (https://daoxe.com) as an OpenAI-compatible model provider

Signed-off-by: seven7763 <seven7763@users.noreply.github.com>
This commit is contained in:
Seven authored and GitHub committed 2026-09-15 19:19:32 +08:00
1 parent 4122ca0561
commit 5ef628396f
12 files changed
+184

No files matched your search

+8
View File
@@ -2047,6 +2047,14 @@
"status": "1",
"llm": []
},
{
"name": "DaoXE",
"logo": "",
"tags": "LLM,TEXT EMBEDDING",
"status": "1",
"llm": [],
"rank": "885"
},
{
"name": "DeepSeek",
"logo": "",
+13
View File
@@ -0,0 +1,13 @@
{
"name": "DaoXE",
"url": {
"default": "https://api.daoxe.com/v1"
},
"url_suffix": {
"chat": "chat/completions",
"models": "models",
"embedding": "embeddings"
},
"class": "daoxe",
"models": []
}
+1
View File
@@ -33,6 +33,7 @@ A complete list of model providers supported by RAGFlow, which will continue to
| Bedrock | `https://aws.amazon.com/bedrock/` |
| Cohere | `https://cohere.com` |
| CometAPI | `https://cometapi.com` |
| DaoXE | `https://daoxe.com` |
| DeepInfra | `https://deepinfra.com` |
| DeepSeek | `https://www.deepseek.com` |
| DeerAPI | `https://deerapi.com` |
+43
View File
@@ -0,0 +1,43 @@
//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package models
// DaoxeModel implements DaoXE's OpenAI-compatible chat and model-list APIs.
// DaoXE speaks the OpenAI wire format without deviation, so the embedded
// OpenAIAPICompatibleModel covers every call and nothing is overridden beyond
// the driver's own identity. The live catalog is account-scoped, so model
// listing reads GET /v1/models from the gateway instead of a static list.
type DaoxeModel struct {
*OpenAIAPICompatibleModel
}
// NewDaoxeModel creates a DaoXE model driver.
func NewDaoxeModel(baseURL map[string]string, urlSuffix URLSuffix) *DaoxeModel {
return &DaoxeModel{
OpenAIAPICompatibleModel: NewOpenAIAPICompatibleModel(baseURL, urlSuffix),
}
}
// Name returns the model driver name.
func (m *DaoxeModel) Name() string {
return "DaoXE"
}
// NewInstance creates a new DaoXE driver bound to the given base URL.
func (m *DaoxeModel) NewInstance(baseURL map[string]string) ModelDriver {
return NewDaoxeModel(baseURL, m.baseModel.URLSuffix)
}
+68
View File
@@ -0,0 +1,68 @@
package models
import (
"encoding/json"
"net/http"
"net/http/httptest"
"ragflow/internal/common"
"testing"
)
func newDaoxeForTest(baseURL string) *DaoxeModel {
return NewDaoxeModel(
map[string]string{"default": baseURL},
URLSuffix{Chat: "chat/completions", Models: "models", Embedding: "embeddings"},
)
}
func TestDaoxeName(t *testing.T) {
if got := newDaoxeForTest("http://unused").Name(); got != "DaoXE" {
t.Errorf("Name()=%q", got)
}
}
func TestDaoxeFactory(t *testing.T) {
driver, err := NewModelFactory().CreateModelDriver("daoxe", map[string]string{"default": "http://unused"}, URLSuffix{})
if err != nil {
t.Fatalf("CreateModelDriver: %v", err)
}
if _, ok := driver.(*DaoxeModel); !ok {
t.Fatalf("driver type=%T, want *DaoxeModel", driver)
}
if _, ok := driver.NewInstance(map[string]string{"default": "http://other"}).(*DaoxeModel); !ok {
t.Fatal("NewInstance did not return *DaoxeModel")
}
}
// DaoXE is a hosted gateway, so unlike a local server it must receive the
// tenant's key as a bearer token on every call.
func TestDaoxeChatSendsAPIKey(t *testing.T) {
withSSRFBypass(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/chat/completions" {
t.Errorf("path=%s", r.URL.Path)
}
if got, want := r.Header.Get("Authorization"), "Bearer "+testAPIKey; got != want {
t.Errorf("Authorization=%q, want %q", got, want)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"id": "chat-daoxe",
"choices": []map[string]any{{"message": map[string]any{"content": "pong"}}},
"usage": map[string]any{"prompt_tokens": 3, "completion_tokens": 5, "total_tokens": 8},
})
}))
defer srv.Close()
usage := &common.ModelUsage{}
resp, err := newDaoxeForTest(srv.URL).ChatWithMessages(
t.Context(), "gpt-4o", []Message{{Role: "user", Content: "ping"}},
&APIConfig{ApiKey: &testAPIKey}, nil, usage,
)
if err != nil {
t.Fatalf("ChatWithMessages: %v", err)
}
if *resp.Answer != "pong" {
t.Errorf("Answer=%q", *resp.Answer)
}
assertModelUsage(t, usage, 3, 5, 8)
}
+2
View File
@@ -87,6 +87,8 @@ func (f *ModelFactory) CreateModelDriver(providerName string, baseURL map[string
return NewCoHereModel(baseURL, urlSuffix), nil
case "cometapi":
return NewCometAPIModel(baseURL, urlSuffix), nil
case "daoxe":
return NewDaoxeModel(baseURL, urlSuffix), nil
case "fish audio":
return NewFishAudioModel(baseURL, urlSuffix), nil
case "mistral":
+18
View File
@@ -3015,6 +3015,24 @@ class RAGconChat(Base):
super().__init__(key, model_name, base_url, **kwargs)
class DaoXEChat(Base):
"""DaoXE OpenAI-compatible chat adapter.
DaoXE is a hosted multi-model gateway speaking the OpenAI wire format, so
the standard OpenAI client path covers every call. The base URL is
tenant-configurable because DaoXE is also self-hostable behind custom
domains.
"""
_FACTORY_NAME = "DaoXE"
def __init__(self, key, model_name, base_url, **kwargs):
if not base_url:
raise ValueError("url cannot be None")
model_name = model_name.split("___")[0]
super().__init__(key, model_name, base_url, **kwargs)
class NewAPIChat(Base):
_FACTORY_NAME = "New API"
+12
View File
@@ -1491,6 +1491,18 @@ class PerplexityEmbed(Base):
return np.array(embds[0]), cnt
class DaoXEEmbed(OpenAIEmbed):
"""DaoXE OpenAI-compatible embeddings."""
_FACTORY_NAME = "DaoXE"
def __init__(self, key, model_name, base_url):
if not base_url:
raise ValueError("url cannot be None")
self.client = OpenAI(api_key=key, base_url=base_url)
self.model_name = model_name.split("___")[0]
class NewAPIEmbed(OpenAIEmbed):
_FACTORY_NAME = "New API"
+12
View File
@@ -1187,6 +1187,18 @@ class ApiRoute(OpenAIAPICompatible):
return f"{self._BASE_URL}/models"
class DaoXE(OpenAIAPICompatible):
"""DaoXE catalog lister.
The live catalog is account-scoped and changes over time, so the list is
read from the gateway's own ``/v1/models`` endpoint (inherited behavior)
rather than pinned in ``conf/models/daoxe.json``, which stays empty on
purpose.
"""
_FACTORY_NAME = "DaoXE"
class NewAPI(OpenAIAPICompatible):
_FACTORY_NAME = "New API"
+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 192 192" width="192" height="192">
<image width="192" height="192" xlink:href="data:image/png;base64,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 truncated
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

+1
View File
@@ -95,6 +95,7 @@ const svgIcons = [
LLMFactory.RAGcon,
LLMFactory.SoMark,
LLMFactory.NewAPI,
LLMFactory.DaoXE,
LLMFactory.Astraflow,
LLMFactory.AstraflowCN,
LLMFactory.FuturMix,
+3
View File
@@ -103,6 +103,7 @@ export enum LLMFactory {
Qiniu = 'Qiniu',
TokenHub = 'TokenHub',
NewAPI = 'New API',
DaoXE = 'DaoXE',
FunASR = 'FunASR',
AIMLAPI = 'aimlapi.com',
GreenPT = 'GreenPT',
@@ -193,6 +194,7 @@ export const IconMap = {
[LLMFactory.TokenHub]: 'tokenhub',
[LLMFactory.SoMark]: 'somark',
[LLMFactory.NewAPI]: 'new-api',
[LLMFactory.DaoXE]: 'daoxe',
[LLMFactory.FunASR]: 'funasr',
[LLMFactory.AIMLAPI]: 'aimlapi',
[LLMFactory.GreenPT]: 'greenpt',
@@ -225,6 +227,7 @@ export const APIMapUrl = {
[LLMFactory.GreenPT]: 'https://greenpt.ai',
[LLMFactory.Synthorai]: 'https://synthorai.io',
[LLMFactory.ApiRoute]: 'https://www.api-route.com',
[LLMFactory.DaoXE]: 'https://daoxe.com',
[LLMFactory.MWS]:
'https://mws.ru/docs/cloud-platform/gpt/general/inference-text.html',
[LLMFactory.Anthropic]: 'https://console.anthropic.com/settings/keys',