feat: add GreenPT model provider (#17447)

## Summary

GreenPT is a European AI provider with an OpenAI-compatible API,
optimized infrastructure, and datacenters powered by 100% renewable
energy.

This adds native GreenPT support across RAGFlow’s Go-first provider
system and its Python compatibility layer:

- discovers the current catalog from `GET /v1/models`
- features `glm-5.2` and `kimi-k2.7-code` for chat and coding
- supports `green-embedding` through `/v1/embeddings`
- supports `green-rerank` through `/v1/rerank`
- supports `green-s` and `green-s-pro` speech-to-text through
`/v1/listen`
- adds provider configuration, UI icon, and supported-provider
documentation
This commit is contained in:
Robert Keus
2026-07-28 13:19:00 +02:00
committed by GitHub
parent 55a5254045
commit 7e1ab9741b
15 changed files with 624 additions and 2 deletions

View File

@@ -1,5 +1,57 @@
{
"factory_llm_infos": [
{
"name": "GreenPT",
"logo": "",
"tags": "LLM,TEXT EMBEDDING,TEXT RE-RANK,SPEECH2TEXT",
"status": "1",
"rank": "50",
"url": "https://api.greenpt.ai/v1",
"llm": [
{
"llm_name": "glm-5.2",
"tags": "LLM,CHAT,1M",
"max_tokens": 1000000,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "kimi-k2.7-code",
"tags": "LLM,CHAT,256K",
"max_tokens": 262144,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "green-embedding",
"tags": "TEXT EMBEDDING,32K",
"max_tokens": 32768,
"model_type": "embedding",
"is_tools": false
},
{
"llm_name": "green-rerank",
"tags": "RE-RANK,32K",
"max_tokens": 32768,
"model_type": "rerank",
"is_tools": false
},
{
"llm_name": "green-s",
"tags": "SPEECH2TEXT",
"max_tokens": 0,
"model_type": "speech2text",
"is_tools": false
},
{
"llm_name": "green-s-pro",
"tags": "SPEECH2TEXT",
"max_tokens": 0,
"model_type": "speech2text",
"is_tools": false
}
]
},
{
"name": "aimlapi.com",
"logo": "",

68
conf/models/greenpt.json Normal file
View File

@@ -0,0 +1,68 @@
{
"name": "GreenPT",
"url": {
"default": "https://api.greenpt.ai"
},
"url_suffix": {
"chat": "v1/chat/completions",
"models": "v1/models",
"embedding": "v1/embeddings",
"rerank": "v1/rerank",
"asr": "v1/listen"
},
"class": "greenpt",
"models": [
{
"name": "glm-5.2",
"max_tokens": 1000000,
"model_types": [
"chat"
],
"tools": {
"support": true
}
},
{
"name": "kimi-k2.7-code",
"max_tokens": 262144,
"model_types": [
"chat"
],
"tools": {
"support": true
}
},
{
"name": "green-embedding",
"max_tokens": 32768,
"max_dimension": 2560,
"dimensions": [
2560
],
"model_types": [
"embedding"
]
},
{
"name": "green-rerank",
"max_tokens": 32768,
"model_types": [
"rerank"
]
},
{
"name": "green-s",
"max_tokens": 0,
"model_types": [
"asr"
]
},
{
"name": "green-s-pro",
"max_tokens": 0,
"model_types": [
"asr"
]
}
]
}

View File

@@ -35,6 +35,7 @@ A complete list of model providers supported by RAGFlow, which will continue to
| Google Cloud | `https://cloud.google.com` |
| GPUStack | `https://gpustack.ai` |
| Groq | `https://groq.com` |
| GreenPT | `https://greenpt.ai` |
| HuggingFace | `https://huggingface.co` |
| Jina | `https://jina.ai` |
| LocalAI | `https://localai.io` |

View File

@@ -159,6 +159,8 @@ func (f *ModelFactory) CreateModelDriver(providerName string, baseURL map[string
return NewXiaomiModel(baseURL, urlSuffix), nil
case "funasr":
return NewFunASRModel(baseURL, urlSuffix), nil
case "greenpt":
return NewGreenPTModel(baseURL, urlSuffix), nil
default:
return NewDummyModel(baseURL, urlSuffix), nil
}

View File

@@ -0,0 +1,145 @@
//
// 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
import (
"context"
"encoding/json"
"fmt"
"io"
"mime"
"net/http"
"net/url"
"os"
"path/filepath"
"ragflow/internal/common"
"strings"
)
// GreenPTModel implements GreenPT's OpenAI-compatible chat, embedding,
// reranking, and model-list APIs, plus its Deepgram-compatible STT endpoint.
type GreenPTModel struct {
*OpenAIAPICompatibleModel
}
// NewGreenPTModel creates a GreenPT model driver.
func NewGreenPTModel(baseURL map[string]string, urlSuffix URLSuffix) *GreenPTModel {
return &GreenPTModel{
OpenAIAPICompatibleModel: NewOpenAIAPICompatibleModel(baseURL, urlSuffix),
}
}
func (m *GreenPTModel) Name() string {
return "GreenPT"
}
func (m *GreenPTModel) NewInstance(baseURL map[string]string) ModelDriver {
return NewGreenPTModel(baseURL, m.baseModel.URLSuffix)
}
// ListModels uses GreenPT's live /v1/models endpoint and corrects the two
// speech model IDs, whose names do not contain an ASR hint.
func (m *GreenPTModel) ListModels(ctx context.Context, apiConfig *APIConfig) ([]ListModelResponse, error) {
models, err := m.OpenAIAPICompatibleModel.ListModels(ctx, apiConfig)
if err != nil {
return nil, err
}
for i := range models {
switch models[i].Name {
case "green-s", "green-s-pro":
models[i].ModelTypes = []string{"asr"}
}
}
return models, nil
}
// TranscribeAudio calls GreenPT's Deepgram-compatible /v1/listen endpoint.
func (m *GreenPTModel) TranscribeAudio(ctx context.Context, modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, modelUsage *common.ModelUsage) (*ASRResponse, error) {
if err := m.baseModel.APIConfigCheck(apiConfig); err != nil {
return nil, err
}
if modelName == nil || strings.TrimSpace(*modelName) == "" {
return nil, fmt.Errorf("model name is required")
}
if file == nil || strings.TrimSpace(*file) == "" {
return nil, fmt.Errorf("file is missing")
}
ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout)
defer cancel()
audio, err := os.Open(*file)
if err != nil {
return nil, fmt.Errorf("failed to open audio file: %w", err)
}
defer audio.Close()
baseURL, err := m.baseModel.GetBaseURL(apiConfig)
if err != nil {
return nil, err
}
endpoint := fmt.Sprintf("%s/%s", strings.TrimSuffix(baseURL, "/"), strings.TrimPrefix(m.baseModel.URLSuffix.ASR, "/"))
query := url.Values{"model": {*modelName}}
if asrConfig != nil {
for key, value := range asrConfig.Params {
query.Set(key, fmt.Sprintf("%v", value))
}
}
endpoint += "?" + query.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, audio)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
contentType := mime.TypeByExtension(strings.ToLower(filepath.Ext(*file)))
if contentType == "" {
contentType = "application/octet-stream"
}
req.Header.Set("Content-Type", contentType)
req.Header.Set("Authorization", "Token "+*apiConfig.ApiKey)
resp, err := m.baseModel.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("GreenPT ASR API error: %s, body: %s", resp.Status, string(body))
}
var result struct {
Results struct {
Channels []struct {
Alternatives []struct {
Transcript string `json:"transcript"`
} `json:"alternatives"`
} `json:"channels"`
} `json:"results"`
}
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to parse GreenPT ASR response: %w", err)
}
if len(result.Results.Channels) == 0 || len(result.Results.Channels[0].Alternatives) == 0 {
return nil, fmt.Errorf("GreenPT ASR response contains no transcript")
}
return &ASRResponse{Text: result.Results.Channels[0].Alternatives[0].Transcript}, nil
}

View File

@@ -0,0 +1,115 @@
//
// 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
import (
"context"
"io"
"net/http"
"net/http/httptest"
"os"
"testing"
)
func TestGreenPTListModelsClassifiesSpeech(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/models" {
t.Errorf("unexpected path: %s", r.URL.Path)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = io.WriteString(w, `{"object":"list","data":[{"id":"glm-5.2"},{"id":"green-embedding"},{"id":"green-rerank"},{"id":"green-s"},{"id":"green-s-pro"}]}`)
}))
defer server.Close()
driver := NewGreenPTModel(map[string]string{"default": server.URL}, URLSuffix{Models: "v1/models"})
key := "test"
models, err := driver.ListModels(context.Background(), &APIConfig{ApiKey: &key})
if err != nil {
t.Fatal(err)
}
want := map[string]string{
"glm-5.2": "chat",
"green-embedding": "embedding",
"green-rerank": "rerank",
"green-s": "asr",
"green-s-pro": "asr",
}
if len(models) != len(want) {
t.Fatalf("expected %d models, got %d", len(want), len(models))
}
for _, model := range models {
expectedType, ok := want[model.Name]
if !ok {
t.Fatalf("unexpected model: %s", model.Name)
}
if len(model.ModelTypes) != 1 || model.ModelTypes[0] != expectedType {
t.Fatalf("%s classified as %v", model.Name, model.ModelTypes)
}
delete(want, model.Name)
}
if len(want) != 0 {
t.Fatalf("missing expected models: %v", want)
}
}
func TestGreenPTTranscribeAudio(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/listen" || r.URL.Query().Get("model") != "green-s" {
t.Errorf("unexpected request URL: %s", r.URL.String())
return
}
if r.Header.Get("Authorization") != "Token test-key" {
t.Errorf("unexpected authorization header: %s", r.Header.Get("Authorization"))
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
t.Errorf("failed to read request body: %v", err)
return
}
if string(body) != "audio" {
t.Errorf("unexpected audio body: %q", body)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = io.WriteString(w, `{"results":{"channels":[{"alternatives":[{"transcript":"renewable inference"}]}]}}`)
}))
defer server.Close()
audio, err := os.CreateTemp(t.TempDir(), "*.wav")
if err != nil {
t.Fatal(err)
}
if _, err = audio.WriteString("audio"); err != nil {
t.Fatal(err)
}
if err = audio.Close(); err != nil {
t.Fatal(err)
}
driver := NewGreenPTModel(map[string]string{"default": server.URL}, URLSuffix{ASR: "v1/listen"})
key, model, path := "test-key", "green-s", audio.Name()
response, err := driver.TranscribeAudio(context.Background(), &model, &path, &APIConfig{ApiKey: &key}, nil, nil)
if err != nil {
t.Fatal(err)
}
if response.Text != "renewable inference" {
t.Fatalf("unexpected transcript: %q", response.Text)
}
}

View File

@@ -1563,6 +1563,15 @@ class AIMLAPIChat(Base):
logging.info("[aimlapi.com] Chat initialized with model %s", model_name)
class GreenPTChat(Base):
"""GreenPT OpenAI-compatible chat adapter."""
_FACTORY_NAME = "GreenPT"
def __init__(self, key, model_name, base_url="https://api.greenpt.ai/v1", **kwargs):
super().__init__(key, model_name, base_url or "https://api.greenpt.ai/v1", **kwargs)
class LiteLLMBase(ABC):
_FACTORY_NAME = [
"Tongyi-Qianwen",

View File

@@ -878,6 +878,15 @@ class OpenAI_APIEmbed(OpenAIEmbed):
self.model_name = model_name.split("___")[0]
class GreenPTEmbed(OpenAIEmbed):
"""GreenPT OpenAI-compatible embedding adapter."""
_FACTORY_NAME = "GreenPT"
def __init__(self, key, model_name="green-embedding", base_url="https://api.greenpt.ai/v1"):
super().__init__(key, model_name=model_name, base_url=base_url or "https://api.greenpt.ai/v1")
class CoHereEmbed(Base):
_FACTORY_NAME = "Cohere"

View File

@@ -18,6 +18,7 @@ import aiohttp
from abc import ABC
from urllib.parse import urlparse
from json.decoder import JSONDecodeError
from typing import ClassVar
from common.constants import LLMType
@@ -458,6 +459,37 @@ class OpenAIAPICompatible(Base):
return model_list
class GreenPT(OpenAIAPICompatible):
"""Discover and classify GreenPT models from the live catalog."""
_FACTORY_NAME = "GreenPT"
_MODEL_TYPES: ClassVar[dict[str, list[str]]] = {
"green-embedding": [LLMType.EMBEDDING.value],
"qwen3-embedding-8b": [LLMType.EMBEDDING.value],
"green-rerank": [LLMType.RERANK.value],
"green-s": [LLMType.ASR.value],
"green-s-pro": [LLMType.ASR.value],
}
_MAX_TOKENS: ClassVar[dict[str, int]] = {
"glm-5.2": 1_000_000,
"kimi-k2.7-code": 262_144,
"green-embedding": 32_768,
"qwen3-embedding-8b": 32_768,
"green-rerank": 32_768,
}
def _format_model_list(self, raw_model_list):
"""Apply GreenPT capability metadata to discovered models."""
models = super()._format_model_list(raw_model_list)
for model in models:
model["model_types"] = self._MODEL_TYPES.get(model["name"], model["model_types"])
model["max_tokens"] = self._MAX_TOKENS.get(model["name"], model["max_tokens"])
if model["model_types"] == [LLMType.CHAT.value]:
model["features"] = ["is_tools"]
return models
class FunASR(Base):
_FACTORY_NAME = "FunASR"

View File

@@ -116,6 +116,18 @@ class JinaRerank(Base):
return rank, total_token_count_from_response(res)
class GreenPTRerank(JinaRerank):
"""GreenPT native reranking adapter."""
_FACTORY_NAME = "GreenPT"
def __init__(self, key, model_name="green-rerank", base_url="https://api.greenpt.ai/v1/rerank"):
endpoint = (base_url or "https://api.greenpt.ai/v1/rerank").rstrip("/")
if not endpoint.endswith("/rerank"):
endpoint += "/rerank"
super().__init__(key, model_name=model_name, base_url=endpoint)
class XInferenceRerank(Base):
_FACTORY_NAME = "Xinference"

View File

@@ -16,12 +16,13 @@
import base64
import io
import json
import logging
import os
import re
import struct
from abc import ABC
import tempfile
import logging
from abc import ABC
from collections.abc import Mapping, Sequence
from urllib.parse import urlparse
import requests
@@ -31,6 +32,8 @@ from openai.lib.azure import AzureOpenAI
from common.token_utils import num_tokens_from_string
from rag.utils.url_utils import ensure_v1
logger = logging.getLogger(__name__)
class Base(ABC):
def __init__(self, key, model_name, **kwargs):
@@ -124,6 +127,67 @@ class FuturMixSeq2txt(GPTSeq2txt):
logging.info("[FuturMix] Speech2Text initialized with model %s", model_name)
class GreenPTSeq2txt(Base):
"""Transcribe audio with GreenPT's Deepgram-compatible endpoint."""
_FACTORY_NAME = "GreenPT"
def __init__(self, key, model_name="green-s", base_url="https://api.greenpt.ai/v1", **kwargs):
self.api_key = key
self.model_name = model_name
self.base_url = (base_url or "https://api.greenpt.ai/v1").rstrip("/")
def transcription(self, audio_path, **kwargs):
"""Transcribe audio while logging only non-sensitive request metadata."""
params = {"model": self.model_name}
params.update(kwargs)
logger.info("[GreenPT] Starting speech transcription with model %s", self.model_name)
try:
with open(audio_path, "rb") as audio_file:
response = requests.post(
f"{self.base_url}/listen",
headers={"Authorization": f"Token {self.api_key}", "Content-Type": "application/octet-stream"},
params=params,
data=audio_file,
timeout=300,
)
response.raise_for_status()
except (OSError, requests.RequestException):
logger.exception("[GreenPT] Speech transcription request failed for model %s", self.model_name)
raise
try:
payload = response.json()
if not isinstance(payload, Mapping):
raise TypeError("response root must be an object")
results = payload.get("results")
if not isinstance(results, Mapping):
raise TypeError("results must be an object")
channels = results.get("channels")
if not isinstance(channels, Sequence) or isinstance(channels, (str, bytes)) or not channels:
raise TypeError("channels must be a non-empty array")
channel = channels[0]
if not isinstance(channel, Mapping):
raise TypeError("channel must be an object")
alternatives = channel.get("alternatives")
if not isinstance(alternatives, Sequence) or isinstance(alternatives, (str, bytes)) or not alternatives:
raise TypeError("alternatives must be a non-empty array")
alternative = alternatives[0]
if not isinstance(alternative, Mapping):
raise TypeError("alternative must be an object")
transcript = alternative.get("transcript")
if not isinstance(transcript, str) or not (text := transcript.strip()):
raise TypeError("transcript must be a non-empty string")
except (TypeError, ValueError) as exc:
logger.warning("[GreenPT] Invalid speech response; model=%s status=%s", self.model_name, response.status_code)
raise ValueError("GreenPT speech response contains no valid transcript") from exc
logger.info(
"[GreenPT] Speech transcription completed; status=%s transcript_available=%s",
response.status_code,
bool(text),
)
return text, num_tokens_from_string(text)
class QWenSeq2txt(Base):
_FACTORY_NAME = "Tongyi-Qianwen"
_FUN_ASR_FLASH_PREFIX = "fun-asr-flash"

View File

@@ -0,0 +1,105 @@
#
# 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.
#
import logging
from io import BytesIO
from unittest.mock import Mock, patch
import pytest
from common.constants import LLMType
from rag.llm.model_meta import GreenPT
from rag.llm.rerank_model import GreenPTRerank
from rag.llm.sequence2txt_model import GreenPTSeq2txt
def test_greenpt_model_list_classifies_native_endpoints():
provider = GreenPT("test", "https://api.greenpt.ai/v1")
models = provider._format_model_list(
{
"data": [
{"id": "glm-5.2"},
{"id": "green-embedding"},
{"id": "green-rerank"},
{"id": "green-s"},
]
}
)
by_name = {model["name"]: model for model in models}
assert by_name["glm-5.2"]["model_types"] == [LLMType.CHAT.value]
assert by_name["glm-5.2"]["max_tokens"] == 1_000_000
assert by_name["green-embedding"]["model_types"] == [LLMType.EMBEDDING.value]
assert by_name["green-rerank"]["model_types"] == [LLMType.RERANK.value]
assert by_name["green-s"]["model_types"] == [LLMType.ASR.value]
def test_greenpt_rerank_normalizes_endpoint():
assert GreenPTRerank("test").base_url == "https://api.greenpt.ai/v1/rerank"
assert GreenPTRerank("test", base_url="https://example.com/v1").base_url == "https://example.com/v1/rerank"
def test_greenpt_transcription_uses_listen_protocol(caplog):
caplog.set_level(logging.INFO)
response = Mock()
response.status_code = 200
response.json.return_value = {"results": {"channels": [{"alternatives": [{"transcript": " renewable inference "}]}]}}
response.raise_for_status.return_value = None
with (
patch("builtins.open", return_value=BytesIO(b"audio")),
patch("rag.llm.sequence2txt_model.requests.post", return_value=response) as post,
):
text, _ = GreenPTSeq2txt("secret").transcription("sample.wav", language="en")
assert text == "renewable inference"
assert post.call_args.args[0] == "https://api.greenpt.ai/v1/listen"
assert post.call_args.kwargs["headers"]["Authorization"] == "Token secret"
assert post.call_args.kwargs["params"] == {"model": "green-s", "language": "en"}
assert "status=200" in caplog.text
assert "secret" not in caplog.text
assert "sample.wav" not in caplog.text
@pytest.mark.parametrize(
"payload",
[
[],
{},
{"results": []},
{"results": {"channels": "invalid"}},
{"results": {"channels": [[]]}},
{"results": {"channels": [{"alternatives": "invalid"}]}},
{"results": {"channels": [{"alternatives": [[]]}]}},
{"results": {"channels": [{"alternatives": [{"transcript": 42}]}]}},
{"results": {"channels": [{"alternatives": [{"transcript": " "}]}]}},
],
)
def test_greenpt_transcription_rejects_malformed_responses(payload, caplog):
caplog.set_level(logging.WARNING)
response = Mock(status_code=200)
response.json.return_value = payload
with (
patch("builtins.open", return_value=BytesIO(b"audio")),
patch("rag.llm.sequence2txt_model.requests.post", return_value=response),
pytest.raises(ValueError, match="contains no valid transcript"),
):
GreenPTSeq2txt("secret").transcription("sample.wav")
assert "model=green-s status=200" in caplog.text
assert "secret" not in caplog.text
assert "sample.wav" not in caplog.text

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 512 512" fill="none">
<path fill="#9BE755" d="M255.746 52.773c-112.1 0-202.973 90.874-202.973 202.973S143.646 458.72 255.746 458.72 458.72 367.846 458.72 255.746 367.846 52.773 255.746 52.773Zm6.427 313.932c-60.72 0-109.944-49.527-109.944-110.62s49.224-110.62 109.944-110.62 109.944 49.526 109.944 110.62-49.224 110.62-109.944 110.62Z"/>
<path fill="#4B7F2B" d="M319.923 424.89c-92.214 0-166.969-75.729-166.969-169.144 0-93.416 74.755-169.144 166.969-169.144 20.616 0 40.275 3.965 58.505 10.887-34.322-27.922-77.847-44.716-125.293-44.716-110.658 0-200.362 90.874-200.362 202.973S142.477 458.72 253.135 458.72c47.446 0 90.971-16.794 125.293-44.716-18.23 6.921-37.889 10.887-58.505 10.887Z"/>
</svg>

After

Width:  |  Height:  |  Size: 778 B

View File

@@ -109,6 +109,7 @@ const svgIcons = [
LLMFactory.TokenHub,
LLMFactory.FunASR,
LLMFactory.AIMLAPI,
LLMFactory.GreenPT,
];
export const LlmIcon = ({

View File

@@ -88,6 +88,7 @@ export enum LLMFactory {
NewAPI = 'New API',
FunASR = 'FunASR',
AIMLAPI = 'aimlapi.com',
GreenPT = 'GreenPT',
}
// Please lowercase the file name
@@ -175,6 +176,7 @@ export const IconMap = {
[LLMFactory.NewAPI]: 'new-api',
[LLMFactory.FunASR]: 'funasr',
[LLMFactory.AIMLAPI]: 'aimlapi',
[LLMFactory.GreenPT]: 'greenpt',
};
export const ModelTypeToField: Record<string, string> = {
@@ -198,6 +200,7 @@ export const FieldToModelType: Record<string, string> = {
export const APIMapUrl = {
[LLMFactory.OpenAI]: 'https://platform.openai.com/api-keys',
[LLMFactory.AIMLAPI]: 'https://aimlapi.com/app/keys',
[LLMFactory.GreenPT]: 'https://greenpt.ai',
[LLMFactory.Anthropic]: 'https://console.anthropic.com/settings/keys',
[LLMFactory.Gemini]: 'https://aistudio.google.com/app/apikey',
[LLMFactory.DeepSeek]: 'https://platform.deepseek.com/api_keys',